From 21685d6fc6c6763bfce0d26645dddc543ba0ba92 Mon Sep 17 00:00:00 2001 From: DaneEveritt Date: Wed, 4 May 2022 17:46:15 -0400 Subject: [PATCH 001/458] Update composer dependencies --- composer.json | 66 +- composer.lock | 3635 ++++++++++++++++++++++++++----------------------- 2 files changed, 1943 insertions(+), 1758 deletions(-) diff --git a/composer.json b/composer.json index 5c077a4bf6..1d6536e84a 100644 --- a/composer.json +++ b/composer.json @@ -11,48 +11,48 @@ } ], "require": { - "php": "^7.4 || ^8.0", + "php": "^7.4 || ^8.0 || ^8.1", "ext-json": "*", "ext-mbstring": "*", "ext-pdo": "*", "ext-pdo_mysql": "*", "ext-zip": "*", "aws/aws-sdk-php": "^3.171", - "doctrine/dbal": "^2.12", - "fideloper/proxy": "^4.4", - "guzzlehttp/guzzle": "^7.2", - "hashids/hashids": "^4.1", - "laracasts/utilities": "^3.2", - "laravel/framework": "^8.22", - "laravel/helpers": "^1.4", - "laravel/tinker": "^2.5", - "laravel/ui": "^3.0", - "lcobucci/jwt": "^4.0", - "league/flysystem-aws-s3-v3": "^1.0", - "league/flysystem-memory": "^1.0", - "matriphe/iso-639": "^1.2", - "pragmarx/google2fa": "^5.0", - "predis/predis": "^1.1", - "prologue/alerts": "^0.4", - "psr/cache": "1.0.1", - "s1lentium/iptools": "^1.1", - "spatie/laravel-fractal": "^5.8", - "spatie/laravel-query-builder": "^3.3", - "staudenmeir/belongs-to-through": "^2.11", - "symfony/yaml": "^4.4", - "webmozart/assert": "^1.9" + "doctrine/dbal": "~2.13.9", + "fideloper/proxy": "~4.4.1", + "guzzlehttp/guzzle": "~7.4.2", + "hashids/hashids": "~4.1.0", + "laracasts/utilities": "~3.2.1", + "laravel/framework": "^8.83", + "laravel/helpers": "~1.5.0", + "laravel/tinker": "~2.7.2", + "laravel/ui": "~3.4.5", + "lcobucci/jwt": "~4.1.5", + "league/flysystem-aws-s3-v3": "~1.0.29", + "league/flysystem-memory": "~1.0.2", + "matriphe/iso-639": "~1.2.0", + "pragmarx/google2fa": "~5.0.0", + "predis/predis": "~1.1.10", + "prologue/alerts": "~0.4.8", + "psr/cache": "~1.0.1", + "s1lentium/iptools": "~1.1.1", + "spatie/laravel-fractal": "~5.8.1", + "spatie/laravel-query-builder": "~3.6.2", + "staudenmeir/belongs-to-through": "~2.11.2", + "symfony/yaml": "~4.4.37", + "webmozart/assert": "~1.10.0" }, "require-dev": { - "barryvdh/laravel-debugbar": "^3.5", - "barryvdh/laravel-ide-helper": "^2.9", - "facade/ignition": "^2.5", - "fakerphp/faker": "^1.13", - "friendsofphp/php-cs-fixer": "^2.18", - "laravel/dusk": "^6.11", - "mockery/mockery": "^1.4", - "nunomaduro/collision": "^5.2", + "barryvdh/laravel-debugbar": "^3.6", + "barryvdh/laravel-ide-helper": "^2.12", + "facade/ignition": "^2.17", + "fakerphp/faker": "^1.19", + "friendsofphp/php-cs-fixer": "^2.19", + "laravel/dusk": "^6.23", + "mockery/mockery": "^1.5", + "nunomaduro/collision": "^5.11", "php-mock/php-mock-phpunit": "^2.6", - "phpunit/phpunit": "^9.0" + "phpunit/phpunit": "^9.5" }, "autoload": { "files": [ diff --git a/composer.lock b/composer.lock index a45f81aa2f..4685158ce3 100644 --- a/composer.lock +++ b/composer.lock @@ -4,29 +4,80 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "87961b026d9057d13cfc3f9cb21d367d", + "content-hash": "accb145afef8466a6ff78639f2b7b097", "packages": [ + { + "name": "aws/aws-crt-php", + "version": "v1.0.2", + "source": { + "type": "git", + "url": "https://github.com/awslabs/aws-crt-php.git", + "reference": "3942776a8c99209908ee0b287746263725685732" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/awslabs/aws-crt-php/zipball/3942776a8c99209908ee0b287746263725685732", + "reference": "3942776a8c99209908ee0b287746263725685732", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35|^5.4.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "AWS SDK Common Runtime Team", + "email": "aws-sdk-common-runtime@amazon.com" + } + ], + "description": "AWS Common Runtime for PHP", + "homepage": "http://aws.amazon.com/sdkforphp", + "keywords": [ + "amazon", + "aws", + "crt", + "sdk" + ], + "support": { + "issues": "https://github.com/awslabs/aws-crt-php/issues", + "source": "https://github.com/awslabs/aws-crt-php/tree/v1.0.2" + }, + "time": "2021-09-03T22:57:30+00:00" + }, { "name": "aws/aws-sdk-php", - "version": "3.184.6", + "version": "3.222.5", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "0b7187c96ced465d400ad9427157e05ddee68edc" + "reference": "26596820b043db985bf7c9deb98aa2234f6054cb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/0b7187c96ced465d400ad9427157e05ddee68edc", - "reference": "0b7187c96ced465d400ad9427157e05ddee68edc", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/26596820b043db985bf7c9deb98aa2234f6054cb", + "reference": "26596820b043db985bf7c9deb98aa2234f6054cb", "shasum": "" }, "require": { + "aws/aws-crt-php": "^1.0.2", "ext-json": "*", "ext-pcre": "*", "ext-simplexml": "*", - "guzzlehttp/guzzle": "^5.3.3|^6.2.1|^7.0", + "guzzlehttp/guzzle": "^5.3.3 || ^6.2.1 || ^7.0", "guzzlehttp/promises": "^1.4.0", - "guzzlehttp/psr7": "^1.7.0", + "guzzlehttp/psr7": "^1.7.0 || ^2.1.1", "mtdowling/jmespath.php": "^2.6", "php": ">=5.5" }, @@ -41,7 +92,7 @@ "ext-sockets": "*", "nette/neon": "^2.3", "paragonie/random_compat": ">= 2", - "phpunit/phpunit": "^4.8.35|^5.4.3", + "phpunit/phpunit": "^4.8.35 || ^5.6.3", "psr/cache": "^1.0", "psr/simple-cache": "^1.0", "sebastian/comparator": "^1.2.3" @@ -60,12 +111,12 @@ } }, "autoload": { - "psr-4": { - "Aws\\": "src/" - }, "files": [ "src/functions.php" - ] + ], + "psr-4": { + "Aws\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -92,22 +143,22 @@ "support": { "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.184.6" + "source": "https://github.com/aws/aws-sdk-php/tree/3.222.5" }, - "time": "2021-06-17T18:24:56+00:00" + "time": "2022-05-04T20:25:34+00:00" }, { "name": "brick/math", - "version": "0.9.2", + "version": "0.9.3", "source": { "type": "git", "url": "https://github.com/brick/math.git", - "reference": "dff976c2f3487d42c1db75a3b180e2b9f0e72ce0" + "reference": "ca57d18f028f84f777b2168cd1911b0dee2343ae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/brick/math/zipball/dff976c2f3487d42c1db75a3b180e2b9f0e72ce0", - "reference": "dff976c2f3487d42c1db75a3b180e2b9f0e72ce0", + "url": "https://api.github.com/repos/brick/math/zipball/ca57d18f028f84f777b2168cd1911b0dee2343ae", + "reference": "ca57d18f028f84f777b2168cd1911b0dee2343ae", "shasum": "" }, "require": { @@ -117,7 +168,7 @@ "require-dev": { "php-coveralls/php-coveralls": "^2.2", "phpunit/phpunit": "^7.5.15 || ^8.5 || ^9.0", - "vimeo/psalm": "4.3.2" + "vimeo/psalm": "4.9.2" }, "type": "library", "autoload": { @@ -142,36 +193,114 @@ ], "support": { "issues": "https://github.com/brick/math/issues", - "source": "https://github.com/brick/math/tree/0.9.2" + "source": "https://github.com/brick/math/tree/0.9.3" }, "funding": [ + { + "url": "https://github.com/BenMorel", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/brick/math", "type": "tidelift" } ], - "time": "2021-01-20T22:51:39+00:00" + "time": "2021-08-15T20:50:18+00:00" + }, + { + "name": "dflydev/dot-access-data", + "version": "v3.0.1", + "source": { + "type": "git", + "url": "https://github.com/dflydev/dflydev-dot-access-data.git", + "reference": "0992cc19268b259a39e86f296da5f0677841f42c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/0992cc19268b259a39e86f296da5f0677841f42c", + "reference": "0992cc19268b259a39e86f296da5f0677841f42c", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^0.12.42", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.3", + "scrutinizer/ocular": "1.6.0", + "squizlabs/php_codesniffer": "^3.5", + "vimeo/psalm": "^3.14" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Dflydev\\DotAccessData\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Dragonfly Development Inc.", + "email": "info@dflydev.com", + "homepage": "http://dflydev.com" + }, + { + "name": "Beau Simensen", + "email": "beau@dflydev.com", + "homepage": "http://beausimensen.com" + }, + { + "name": "Carlos Frutos", + "email": "carlos@kiwing.it", + "homepage": "https://github.com/cfrutos" + }, + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com" + } + ], + "description": "Given a deep data structure, access data by dot notation.", + "homepage": "https://github.com/dflydev/dflydev-dot-access-data", + "keywords": [ + "access", + "data", + "dot", + "notation" + ], + "support": { + "issues": "https://github.com/dflydev/dflydev-dot-access-data/issues", + "source": "https://github.com/dflydev/dflydev-dot-access-data/tree/v3.0.1" + }, + "time": "2021-08-13T13:06:58+00:00" }, { "name": "doctrine/cache", - "version": "1.11.3", + "version": "2.1.1", "source": { "type": "git", "url": "https://github.com/doctrine/cache.git", - "reference": "3bb5588cec00a0268829cc4a518490df6741af9d" + "reference": "331b4d5dbaeab3827976273e9356b3b453c300ce" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/cache/zipball/3bb5588cec00a0268829cc4a518490df6741af9d", - "reference": "3bb5588cec00a0268829cc4a518490df6741af9d", + "url": "https://api.github.com/repos/doctrine/cache/zipball/331b4d5dbaeab3827976273e9356b3b453c300ce", + "reference": "331b4d5dbaeab3827976273e9356b3b453c300ce", "shasum": "" }, "require": { "php": "~7.1 || ^8.0" }, "conflict": { - "doctrine/common": ">2.2,<2.4", - "psr/cache": ">=3" + "doctrine/common": ">2.2,<2.4" }, "require-dev": { "alcaeus/mongo-php-adapter": "^1.1", @@ -180,8 +309,9 @@ "mongodb/mongodb": "^1.1", "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", "predis/predis": "~1.0", - "psr/cache": "^1.0 || ^2.0", - "symfony/cache": "^4.4 || ^5.2" + "psr/cache": "^1.0 || ^2.0 || ^3.0", + "symfony/cache": "^4.4 || ^5.2 || ^6.0@dev", + "symfony/var-exporter": "^4.4 || ^5.2 || ^6.0@dev" }, "suggest": { "alcaeus/mongo-php-adapter": "Required to use legacy MongoDB driver" @@ -233,7 +363,7 @@ ], "support": { "issues": "https://github.com/doctrine/cache/issues", - "source": "https://github.com/doctrine/cache/tree/1.11.3" + "source": "https://github.com/doctrine/cache/tree/2.1.1" }, "funding": [ { @@ -249,37 +379,39 @@ "type": "tidelift" } ], - "time": "2021-05-25T09:01:55+00:00" + "time": "2021-07-17T14:49:29+00:00" }, { "name": "doctrine/dbal", - "version": "2.13.1", + "version": "2.13.9", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "c800380457948e65bbd30ba92cc17cda108bf8c9" + "reference": "c480849ca3ad6706a39c970cdfe6888fa8a058b8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/c800380457948e65bbd30ba92cc17cda108bf8c9", - "reference": "c800380457948e65bbd30ba92cc17cda108bf8c9", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/c480849ca3ad6706a39c970cdfe6888fa8a058b8", + "reference": "c480849ca3ad6706a39c970cdfe6888fa8a058b8", "shasum": "" }, "require": { - "doctrine/cache": "^1.0", - "doctrine/deprecations": "^0.5.3", + "doctrine/cache": "^1.0|^2.0", + "doctrine/deprecations": "^0.5.3|^1", "doctrine/event-manager": "^1.0", "ext-pdo": "*", "php": "^7.1 || ^8" }, "require-dev": { - "doctrine/coding-standard": "8.2.0", - "jetbrains/phpstorm-stubs": "2020.2", - "phpstan/phpstan": "0.12.81", - "phpunit/phpunit": "^7.5.20|^8.5|9.5.0", - "squizlabs/php_codesniffer": "3.6.0", + "doctrine/coding-standard": "9.0.0", + "jetbrains/phpstorm-stubs": "2021.1", + "phpstan/phpstan": "1.4.6", + "phpunit/phpunit": "^7.5.20|^8.5|9.5.16", + "psalm/plugin-phpunit": "0.16.1", + "squizlabs/php_codesniffer": "3.6.2", + "symfony/cache": "^4.4", "symfony/console": "^2.0.5|^3.0|^4.0|^5.0", - "vimeo/psalm": "4.6.4" + "vimeo/psalm": "4.22.0" }, "suggest": { "symfony/console": "For helpful console commands such as SQL execution and import of files." @@ -340,7 +472,7 @@ ], "support": { "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/2.13.1" + "source": "https://github.com/doctrine/dbal/tree/2.13.9" }, "funding": [ { @@ -356,29 +488,29 @@ "type": "tidelift" } ], - "time": "2021-04-17T17:30:19+00:00" + "time": "2022-05-02T20:28:55+00:00" }, { "name": "doctrine/deprecations", - "version": "v0.5.3", + "version": "v1.0.0", "source": { "type": "git", "url": "https://github.com/doctrine/deprecations.git", - "reference": "9504165960a1f83cc1480e2be1dd0a0478561314" + "reference": "0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/deprecations/zipball/9504165960a1f83cc1480e2be1dd0a0478561314", - "reference": "9504165960a1f83cc1480e2be1dd0a0478561314", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de", + "reference": "0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de", "shasum": "" }, "require": { "php": "^7.1|^8.0" }, "require-dev": { - "doctrine/coding-standard": "^6.0|^7.0|^8.0", - "phpunit/phpunit": "^7.0|^8.0|^9.0", - "psr/log": "^1.0" + "doctrine/coding-standard": "^9", + "phpunit/phpunit": "^7.5|^8.5|^9.5", + "psr/log": "^1|^2|^3" }, "suggest": { "psr/log": "Allows logging deprecations via PSR-3 logger implementation" @@ -397,9 +529,9 @@ "homepage": "https://www.doctrine-project.org/", "support": { "issues": "https://github.com/doctrine/deprecations/issues", - "source": "https://github.com/doctrine/deprecations/tree/v0.5.3" + "source": "https://github.com/doctrine/deprecations/tree/v1.0.0" }, - "time": "2021-03-21T12:59:47+00:00" + "time": "2022-05-02T15:47:09+00:00" }, { "name": "doctrine/event-manager", @@ -497,34 +629,30 @@ }, { "name": "doctrine/inflector", - "version": "2.0.3", + "version": "2.0.4", "source": { "type": "git", "url": "https://github.com/doctrine/inflector.git", - "reference": "9cf661f4eb38f7c881cac67c75ea9b00bf97b210" + "reference": "8b7ff3e4b7de6b2c84da85637b59fd2880ecaa89" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/inflector/zipball/9cf661f4eb38f7c881cac67c75ea9b00bf97b210", - "reference": "9cf661f4eb38f7c881cac67c75ea9b00bf97b210", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/8b7ff3e4b7de6b2c84da85637b59fd2880ecaa89", + "reference": "8b7ff3e4b7de6b2c84da85637b59fd2880ecaa89", "shasum": "" }, "require": { "php": "^7.2 || ^8.0" }, "require-dev": { - "doctrine/coding-standard": "^7.0", - "phpstan/phpstan": "^0.11", - "phpstan/phpstan-phpunit": "^0.11", - "phpstan/phpstan-strict-rules": "^0.11", - "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" + "doctrine/coding-standard": "^8.2", + "phpstan/phpstan": "^0.12", + "phpstan/phpstan-phpunit": "^0.12", + "phpstan/phpstan-strict-rules": "^0.12", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", + "vimeo/psalm": "^4.10" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, "autoload": { "psr-4": { "Doctrine\\Inflector\\": "lib/Doctrine/Inflector" @@ -572,7 +700,7 @@ ], "support": { "issues": "https://github.com/doctrine/inflector/issues", - "source": "https://github.com/doctrine/inflector/tree/2.0.x" + "source": "https://github.com/doctrine/inflector/tree/2.0.4" }, "funding": [ { @@ -588,36 +716,32 @@ "type": "tidelift" } ], - "time": "2020-05-29T15:13:26+00:00" + "time": "2021-10-22T20:16:43+00:00" }, { "name": "doctrine/lexer", - "version": "1.2.1", + "version": "1.2.3", "source": { "type": "git", "url": "https://github.com/doctrine/lexer.git", - "reference": "e864bbf5904cb8f5bb334f99209b48018522f042" + "reference": "c268e882d4dbdd85e36e4ad69e02dc284f89d229" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/lexer/zipball/e864bbf5904cb8f5bb334f99209b48018522f042", - "reference": "e864bbf5904cb8f5bb334f99209b48018522f042", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/c268e882d4dbdd85e36e4ad69e02dc284f89d229", + "reference": "c268e882d4dbdd85e36e4ad69e02dc284f89d229", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0" + "php": "^7.1 || ^8.0" }, "require-dev": { - "doctrine/coding-standard": "^6.0", - "phpstan/phpstan": "^0.11.8", - "phpunit/phpunit": "^8.2" + "doctrine/coding-standard": "^9.0", + "phpstan/phpstan": "^1.3", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "vimeo/psalm": "^4.11" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.2.x-dev" - } - }, "autoload": { "psr-4": { "Doctrine\\Common\\Lexer\\": "lib/Doctrine/Common/Lexer" @@ -652,7 +776,7 @@ ], "support": { "issues": "https://github.com/doctrine/lexer/issues", - "source": "https://github.com/doctrine/lexer/tree/1.2.1" + "source": "https://github.com/doctrine/lexer/tree/1.2.3" }, "funding": [ { @@ -668,33 +792,33 @@ "type": "tidelift" } ], - "time": "2020-05-25T17:44:05+00:00" + "time": "2022-02-28T11:07:21+00:00" }, { "name": "dragonmantank/cron-expression", - "version": "v3.1.0", + "version": "v3.3.1", "source": { "type": "git", "url": "https://github.com/dragonmantank/cron-expression.git", - "reference": "7a8c6e56ab3ffcc538d05e8155bb42269abf1a0c" + "reference": "be85b3f05b46c39bbc0d95f6c071ddff669510fa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/7a8c6e56ab3ffcc538d05e8155bb42269abf1a0c", - "reference": "7a8c6e56ab3ffcc538d05e8155bb42269abf1a0c", + "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/be85b3f05b46c39bbc0d95f6c071ddff669510fa", + "reference": "be85b3f05b46c39bbc0d95f6c071ddff669510fa", "shasum": "" }, "require": { "php": "^7.2|^8.0", - "webmozart/assert": "^1.7.0" + "webmozart/assert": "^1.0" }, "replace": { "mtdowling/cron-expression": "^1.0" }, "require-dev": { "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^0.12", - "phpstan/phpstan-webmozart-assert": "^0.12.7", + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-webmozart-assert": "^1.0", "phpunit/phpunit": "^7.0|^8.0|^9.0" }, "type": "library", @@ -721,7 +845,7 @@ ], "support": { "issues": "https://github.com/dragonmantank/cron-expression/issues", - "source": "https://github.com/dragonmantank/cron-expression/tree/v3.1.0" + "source": "https://github.com/dragonmantank/cron-expression/tree/v3.3.1" }, "funding": [ { @@ -729,7 +853,7 @@ "type": "github" } ], - "time": "2020-11-24T19:55:57+00:00" + "time": "2022-01-18T15:43:28+00:00" }, { "name": "egulias/email-validator", @@ -859,31 +983,26 @@ }, { "name": "graham-campbell/result-type", - "version": "v1.0.1", + "version": "v1.0.4", "source": { "type": "git", "url": "https://github.com/GrahamCampbell/Result-Type.git", - "reference": "7e279d2cd5d7fbb156ce46daada972355cea27bb" + "reference": "0690bde05318336c7221785f2a932467f98b64ca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/7e279d2cd5d7fbb156ce46daada972355cea27bb", - "reference": "7e279d2cd5d7fbb156ce46daada972355cea27bb", + "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/0690bde05318336c7221785f2a932467f98b64ca", + "reference": "0690bde05318336c7221785f2a932467f98b64ca", "shasum": "" }, "require": { - "php": "^7.0|^8.0", - "phpoption/phpoption": "^1.7.3" + "php": "^7.0 || ^8.0", + "phpoption/phpoption": "^1.8" }, "require-dev": { - "phpunit/phpunit": "^6.5|^7.5|^8.5|^9.0" + "phpunit/phpunit": "^6.5.14 || ^7.5.20 || ^8.5.19 || ^9.5.8" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, "autoload": { "psr-4": { "GrahamCampbell\\ResultType\\": "src/" @@ -896,7 +1015,8 @@ "authors": [ { "name": "Graham Campbell", - "email": "graham@alt-three.com" + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" } ], "description": "An Implementation Of The Result Type", @@ -909,7 +1029,7 @@ ], "support": { "issues": "https://github.com/GrahamCampbell/Result-Type/issues", - "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.0.1" + "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.0.4" }, "funding": [ { @@ -921,28 +1041,29 @@ "type": "tidelift" } ], - "time": "2020-04-13T13:17:36+00:00" + "time": "2021-11-21T21:41:47+00:00" }, { "name": "guzzlehttp/guzzle", - "version": "7.3.0", + "version": "7.4.2", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "7008573787b430c1c1f650e3722d9bba59967628" + "reference": "ac1ec1cd9b5624694c3a40be801d94137afb12b4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/7008573787b430c1c1f650e3722d9bba59967628", - "reference": "7008573787b430c1c1f650e3722d9bba59967628", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/ac1ec1cd9b5624694c3a40be801d94137afb12b4", + "reference": "ac1ec1cd9b5624694c3a40be801d94137afb12b4", "shasum": "" }, "require": { "ext-json": "*", - "guzzlehttp/promises": "^1.4", - "guzzlehttp/psr7": "^1.7 || ^2.0", + "guzzlehttp/promises": "^1.5", + "guzzlehttp/psr7": "^1.8.3 || ^2.1", "php": "^7.2.5 || ^8.0", - "psr/http-client": "^1.0" + "psr/http-client": "^1.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" }, "provide": { "psr/http-client-implementation": "1.0" @@ -952,7 +1073,7 @@ "ext-curl": "*", "php-http/client-integration-tests": "^3.0", "phpunit/phpunit": "^8.5.5 || ^9.3.5", - "psr/log": "^1.1" + "psr/log": "^1.1 || ^2.0 || ^3.0" }, "suggest": { "ext-curl": "Required for CURL handler support", @@ -962,35 +1083,59 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "7.3-dev" + "dev-master": "7.4-dev" } }, "autoload": { - "psr-4": { - "GuzzleHttp\\": "src/" - }, "files": [ "src/functions_include.php" - ] + ], + "psr-4": { + "GuzzleHttp\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, { "name": "Michael Dowling", "email": "mtdowling@gmail.com", "homepage": "https://github.com/mtdowling" }, + { + "name": "Jeremy Lindblom", + "email": "jeremeamia@gmail.com", + "homepage": "https://github.com/jeremeamia" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, { "name": "Márk Sági-Kazár", "email": "mark.sagikazar@gmail.com", - "homepage": "https://sagikazarmark.hu" + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" } ], "description": "Guzzle is a PHP HTTP client library", - "homepage": "http://guzzlephp.org/", "keywords": [ "client", "curl", @@ -1004,7 +1149,7 @@ ], "support": { "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.3.0" + "source": "https://github.com/guzzle/guzzle/tree/7.4.2" }, "funding": [ { @@ -1016,28 +1161,24 @@ "type": "github" }, { - "url": "https://github.com/alexeyshockov", - "type": "github" - }, - { - "url": "https://github.com/gmponos", - "type": "github" + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle", + "type": "tidelift" } ], - "time": "2021-03-23T11:33:13+00:00" + "time": "2022-03-20T14:16:28+00:00" }, { "name": "guzzlehttp/promises", - "version": "1.4.1", + "version": "1.5.1", "source": { "type": "git", "url": "https://github.com/guzzle/promises.git", - "reference": "8e7d04f1f6450fef59366c399cfad4b9383aa30d" + "reference": "fe752aedc9fd8fcca3fe7ad05d419d32998a06da" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/8e7d04f1f6450fef59366c399cfad4b9383aa30d", - "reference": "8e7d04f1f6450fef59366c399cfad4b9383aa30d", + "url": "https://api.github.com/repos/guzzle/promises/zipball/fe752aedc9fd8fcca3fe7ad05d419d32998a06da", + "reference": "fe752aedc9fd8fcca3fe7ad05d419d32998a06da", "shasum": "" }, "require": { @@ -1049,26 +1190,41 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.4-dev" + "dev-master": "1.5-dev" } }, "autoload": { - "psr-4": { - "GuzzleHttp\\Promise\\": "src/" - }, "files": [ "src/functions_include.php" - ] + ], + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, { "name": "Michael Dowling", "email": "mtdowling@gmail.com", "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" } ], "description": "Guzzle promises library", @@ -1077,35 +1233,52 @@ ], "support": { "issues": "https://github.com/guzzle/promises/issues", - "source": "https://github.com/guzzle/promises/tree/1.4.1" + "source": "https://github.com/guzzle/promises/tree/1.5.1" }, - "time": "2021-03-07T09:25:29+00:00" + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises", + "type": "tidelift" + } + ], + "time": "2021-10-22T20:56:57+00:00" }, { "name": "guzzlehttp/psr7", - "version": "1.8.2", + "version": "2.2.1", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "dc960a912984efb74d0a90222870c72c87f10c91" + "reference": "c94a94f120803a18554c1805ef2e539f8285f9a2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/dc960a912984efb74d0a90222870c72c87f10c91", - "reference": "dc960a912984efb74d0a90222870c72c87f10c91", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/c94a94f120803a18554c1805ef2e539f8285f9a2", + "reference": "c94a94f120803a18554c1805ef2e539f8285f9a2", "shasum": "" }, "require": { - "php": ">=5.4.0", - "psr/http-message": "~1.0", - "ralouphie/getallheaders": "^2.0.5 || ^3.0.0" + "php": "^7.2.5 || ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0", + "ralouphie/getallheaders": "^3.0" }, "provide": { + "psr/http-factory-implementation": "1.0", "psr/http-message-implementation": "1.0" }, "require-dev": { - "ext-zlib": "*", - "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.10" + "bamarni/composer-bin-plugin": "^1.4.1", + "http-interop/http-factory-tests": "^0.9", + "phpunit/phpunit": "^8.5.8 || ^9.3.10" }, "suggest": { "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" @@ -1113,30 +1286,53 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.7-dev" + "dev-master": "2.2-dev" } }, "autoload": { "psr-4": { "GuzzleHttp\\Psr7\\": "src/" - }, - "files": [ - "src/functions_include.php" - ] + } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, { "name": "Michael Dowling", "email": "mtdowling@gmail.com", "homepage": "https://github.com/mtdowling" }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, { "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", "homepage": "https://github.com/Tobion" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" } ], "description": "PSR-7 message implementation that also provides common utility methods", @@ -1152,9 +1348,23 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/1.8.2" + "source": "https://github.com/guzzle/psr7/tree/2.2.1" }, - "time": "2021-04-26T09:17:50+00:00" + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", + "type": "tidelift" + } + ], + "time": "2022-03-20T21:55:58+00:00" }, { "name": "hashids/hashids", @@ -1228,24 +1438,24 @@ }, { "name": "laracasts/utilities", - "version": "3.2", + "version": "3.2.1", "source": { "type": "git", "url": "https://github.com/laracasts/PHP-Vars-To-Js-Transformer.git", - "reference": "26b1712166a366e3f8a1fd1452d0c3b76cad612a" + "reference": "cfcda21b2425652e869af253d385d78e2129e3a2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laracasts/PHP-Vars-To-Js-Transformer/zipball/26b1712166a366e3f8a1fd1452d0c3b76cad612a", - "reference": "26b1712166a366e3f8a1fd1452d0c3b76cad612a", + "url": "https://api.github.com/repos/laracasts/PHP-Vars-To-Js-Transformer/zipball/cfcda21b2425652e869af253d385d78e2129e3a2", + "reference": "cfcda21b2425652e869af253d385d78e2129e3a2", "shasum": "" }, "require": { - "illuminate/support": "^5.0|^6.0|^7.0|^8.0", - "php": ">=5.5.0|>=7.2.5" + "illuminate/support": "^5.0|^6.0|^7.0|^8.0|^9.0", + "php": ">=5.5.0|>=7.2.5|>=8.0.0" }, "require-dev": { - "phpspec/phpspec": "~2.0" + "phpspec/phpspec": ">=2.0" }, "type": "library", "extra": { @@ -1283,22 +1493,22 @@ ], "support": { "issues": "https://github.com/laracasts/PHP-Vars-To-Js-Transformer/issues", - "source": "https://github.com/laracasts/PHP-Vars-To-Js-Transformer/tree/3.2" + "source": "https://github.com/laracasts/PHP-Vars-To-Js-Transformer/tree/3.2.1" }, - "time": "2020-09-07T13:29:37+00:00" + "time": "2022-02-09T14:09:45+00:00" }, { "name": "laravel/framework", - "version": "v8.47.0", + "version": "v8.83.11", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "93db226946453f4285558b7c3166ddb6e7ea5400" + "reference": "d85c34179f209977043502441f9e44ca432a14b4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/93db226946453f4285558b7c3166ddb6e7ea5400", - "reference": "93db226946453f4285558b7c3166ddb6e7ea5400", + "url": "https://api.github.com/repos/laravel/framework/zipball/d85c34179f209977043502441f9e44ca432a14b4", + "reference": "d85c34179f209977043502441f9e44ca432a14b4", "shasum": "" }, "require": { @@ -1308,34 +1518,37 @@ "ext-json": "*", "ext-mbstring": "*", "ext-openssl": "*", - "league/commonmark": "^1.3", + "laravel/serializable-closure": "^1.0", + "league/commonmark": "^1.3|^2.0.2", "league/flysystem": "^1.1", "monolog/monolog": "^2.0", - "nesbot/carbon": "^2.31", + "nesbot/carbon": "^2.53.1", "opis/closure": "^3.6", "php": "^7.3|^8.0", "psr/container": "^1.0", + "psr/log": "^1.0|^2.0", "psr/simple-cache": "^1.0", - "ramsey/uuid": "^4.0", - "swiftmailer/swiftmailer": "^6.0", - "symfony/console": "^5.1.4", - "symfony/error-handler": "^5.1.4", - "symfony/finder": "^5.1.4", - "symfony/http-foundation": "^5.1.4", - "symfony/http-kernel": "^5.1.4", - "symfony/mime": "^5.1.4", - "symfony/process": "^5.1.4", - "symfony/routing": "^5.1.4", - "symfony/var-dumper": "^5.1.4", + "ramsey/uuid": "^4.2.2", + "swiftmailer/swiftmailer": "^6.3", + "symfony/console": "^5.4", + "symfony/error-handler": "^5.4", + "symfony/finder": "^5.4", + "symfony/http-foundation": "^5.4", + "symfony/http-kernel": "^5.4", + "symfony/mime": "^5.4", + "symfony/process": "^5.4", + "symfony/routing": "^5.4", + "symfony/var-dumper": "^5.4", "tijsverkoyen/css-to-inline-styles": "^2.2.2", - "vlucas/phpdotenv": "^5.2", - "voku/portable-ascii": "^1.4.8" + "vlucas/phpdotenv": "^5.4.1", + "voku/portable-ascii": "^1.6.1" }, "conflict": { "tightenco/collect": "<5.5.33" }, "provide": { - "psr/container-implementation": "1.0" + "psr/container-implementation": "1.0", + "psr/simple-cache-implementation": "1.0" }, "replace": { "illuminate/auth": "self.version", @@ -1371,22 +1584,24 @@ "illuminate/view": "self.version" }, "require-dev": { - "aws/aws-sdk-php": "^3.155", - "doctrine/dbal": "^2.6|^3.0", - "filp/whoops": "^2.8", + "aws/aws-sdk-php": "^3.198.1", + "doctrine/dbal": "^2.13.3|^3.1.4", + "filp/whoops": "^2.14.3", "guzzlehttp/guzzle": "^6.5.5|^7.0.1", "league/flysystem-cached-adapter": "^1.0", - "mockery/mockery": "^1.4.2", - "orchestra/testbench-core": "^6.8", + "mockery/mockery": "^1.4.4", + "orchestra/testbench-core": "^6.27", "pda/pheanstalk": "^4.0", - "phpunit/phpunit": "^8.5.8|^9.3.3", - "predis/predis": "^1.1.2", - "symfony/cache": "^5.1.4" + "phpunit/phpunit": "^8.5.19|^9.5.8", + "predis/predis": "^1.1.9", + "symfony/cache": "^5.4" }, "suggest": { - "aws/aws-sdk-php": "Required to use the SQS queue driver, DynamoDb failed job storage and SES mail driver (^3.155).", + "ably/ably-php": "Required to use the Ably broadcast driver (^1.0).", + "aws/aws-sdk-php": "Required to use the SQS queue driver, DynamoDb failed job storage and SES mail driver (^3.198.1).", "brianium/paratest": "Required to run tests in parallel (^6.0).", - "doctrine/dbal": "Required to rename columns and drop SQLite columns (^2.6|^3.0).", + "doctrine/dbal": "Required to rename columns and drop SQLite columns (^2.13.3|^3.1.4).", + "ext-bcmath": "Required to use the multiple_of validation rule.", "ext-ftp": "Required to use the Flysystem FTP driver.", "ext-gd": "Required to use Illuminate\\Http\\Testing\\FileFactory::image().", "ext-memcached": "Required to use the memcache cache driver.", @@ -1394,21 +1609,21 @@ "ext-posix": "Required to use all features of the queue worker.", "ext-redis": "Required to use the Redis cache and queue drivers (^4.0|^5.0).", "fakerphp/faker": "Required to use the eloquent factory builder (^1.9.1).", - "filp/whoops": "Required for friendly error pages in development (^2.8).", + "filp/whoops": "Required for friendly error pages in development (^2.14.3).", "guzzlehttp/guzzle": "Required to use the HTTP Client, Mailgun mail driver and the ping methods on schedules (^6.5.5|^7.0.1).", "laravel/tinker": "Required to use the tinker console command (^2.0).", "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (^1.0).", "league/flysystem-cached-adapter": "Required to use the Flysystem cache (^1.0).", "league/flysystem-sftp": "Required to use the Flysystem SFTP driver (^1.0).", - "mockery/mockery": "Required to use mocking (^1.4.2).", + "mockery/mockery": "Required to use mocking (^1.4.4).", "nyholm/psr7": "Required to use PSR-7 bridging features (^1.2).", "pda/pheanstalk": "Required to use the beanstalk queue driver (^4.0).", - "phpunit/phpunit": "Required to use assertions and run tests (^8.5.8|^9.3.3).", - "predis/predis": "Required to use the predis connector (^1.1.2).", + "phpunit/phpunit": "Required to use assertions and run tests (^8.5.19|^9.5.8).", + "predis/predis": "Required to use the predis connector (^1.1.9).", "psr/http-message": "Required to allow Storage::put to accept a StreamInterface (^1.0).", - "pusher/pusher-php-server": "Required to use the Pusher broadcast driver (^4.0|^5.0|^6.0).", - "symfony/cache": "Required to PSR-6 cache bridge (^5.1.4).", - "symfony/filesystem": "Required to enable support for relative symbolic links (^5.1.4).", + "pusher/pusher-php-server": "Required to use the Pusher broadcast driver (^4.0|^5.0|^6.0|^7.0).", + "symfony/cache": "Required to PSR-6 cache bridge (^5.4).", + "symfony/filesystem": "Required to enable support for relative symbolic links (^5.4).", "symfony/psr-http-message-bridge": "Required to use PSR-7 bridging features (^2.0).", "wildbit/swiftmailer-postmark": "Required to use Postmark mail driver (^3.0)." }, @@ -1453,24 +1668,24 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2021-06-15T14:00:32+00:00" + "time": "2022-05-03T14:47:00+00:00" }, { "name": "laravel/helpers", - "version": "v1.4.1", + "version": "v1.5.0", "source": { "type": "git", "url": "https://github.com/laravel/helpers.git", - "reference": "febb10d8daaf86123825de2cb87f789a3371f0ac" + "reference": "c28b0ccd799d58564c41a62395ac9511a1e72931" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/helpers/zipball/febb10d8daaf86123825de2cb87f789a3371f0ac", - "reference": "febb10d8daaf86123825de2cb87f789a3371f0ac", + "url": "https://api.github.com/repos/laravel/helpers/zipball/c28b0ccd799d58564c41a62395ac9511a1e72931", + "reference": "c28b0ccd799d58564c41a62395ac9511a1e72931", "shasum": "" }, "require": { - "illuminate/support": "~5.8.0|^6.0|^7.0|^8.0", + "illuminate/support": "~5.8.0|^6.0|^7.0|^8.0|^9.0", "php": "^7.1.3|^8.0" }, "require-dev": { @@ -1498,7 +1713,7 @@ }, { "name": "Dries Vints", - "email": "dries.vints@gmail.com" + "email": "dries@laravel.com" } ], "description": "Provides backwards compatibility for helpers in the latest Laravel release.", @@ -1507,53 +1722,41 @@ "laravel" ], "support": { - "source": "https://github.com/laravel/helpers/tree/v1.4.1" + "source": "https://github.com/laravel/helpers/tree/v1.5.0" }, - "time": "2021-02-16T15:27:11+00:00" + "time": "2022-01-12T15:58:51+00:00" }, { - "name": "laravel/tinker", - "version": "v2.6.1", + "name": "laravel/serializable-closure", + "version": "v1.1.1", "source": { "type": "git", - "url": "https://github.com/laravel/tinker.git", - "reference": "04ad32c1a3328081097a181875733fa51f402083" + "url": "https://github.com/laravel/serializable-closure.git", + "reference": "9e4b005daa20b0c161f3845040046dc9ddc1d74e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/tinker/zipball/04ad32c1a3328081097a181875733fa51f402083", - "reference": "04ad32c1a3328081097a181875733fa51f402083", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/9e4b005daa20b0c161f3845040046dc9ddc1d74e", + "reference": "9e4b005daa20b0c161f3845040046dc9ddc1d74e", "shasum": "" }, "require": { - "illuminate/console": "^6.0|^7.0|^8.0", - "illuminate/contracts": "^6.0|^7.0|^8.0", - "illuminate/support": "^6.0|^7.0|^8.0", - "php": "^7.2.5|^8.0", - "psy/psysh": "^0.10.4", - "symfony/var-dumper": "^4.3.4|^5.0" + "php": "^7.3|^8.0" }, "require-dev": { - "mockery/mockery": "~1.3.3|^1.4.2", - "phpunit/phpunit": "^8.5.8|^9.3.3" - }, - "suggest": { - "illuminate/database": "The Illuminate Database package (^6.0|^7.0|^8.0)." + "pestphp/pest": "^1.18", + "phpstan/phpstan": "^0.12.98", + "symfony/var-dumper": "^5.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.x-dev" - }, - "laravel": { - "providers": [ - "Laravel\\Tinker\\TinkerServiceProvider" - ] + "dev-master": "1.x-dev" } }, "autoload": { "psr-4": { - "Laravel\\Tinker\\": "src/" + "Laravel\\SerializableClosure\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -1564,50 +1767,124 @@ { "name": "Taylor Otwell", "email": "taylor@laravel.com" + }, + { + "name": "Nuno Maduro", + "email": "nuno@laravel.com" } ], - "description": "Powerful REPL for the Laravel framework.", + "description": "Laravel Serializable Closure provides an easy and secure way to serialize closures in PHP.", "keywords": [ - "REPL", - "Tinker", + "closure", "laravel", - "psysh" + "serializable" ], "support": { - "issues": "https://github.com/laravel/tinker/issues", - "source": "https://github.com/laravel/tinker/tree/v2.6.1" + "issues": "https://github.com/laravel/serializable-closure/issues", + "source": "https://github.com/laravel/serializable-closure" }, - "time": "2021-03-02T16:53:12+00:00" + "time": "2022-02-11T19:23:53+00:00" }, { - "name": "laravel/ui", - "version": "v3.3.0", + "name": "laravel/tinker", + "version": "v2.7.2", "source": { "type": "git", - "url": "https://github.com/laravel/ui.git", - "reference": "07d725813350c695c779382cbd6dac0ab8665537" + "url": "https://github.com/laravel/tinker.git", + "reference": "dff39b661e827dae6e092412f976658df82dbac5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/ui/zipball/07d725813350c695c779382cbd6dac0ab8665537", - "reference": "07d725813350c695c779382cbd6dac0ab8665537", + "url": "https://api.github.com/repos/laravel/tinker/zipball/dff39b661e827dae6e092412f976658df82dbac5", + "reference": "dff39b661e827dae6e092412f976658df82dbac5", "shasum": "" }, "require": { - "illuminate/console": "^8.42", - "illuminate/filesystem": "^8.42", - "illuminate/support": "^8.42", - "illuminate/validation": "^8.42", - "php": "^7.3|^8.0" + "illuminate/console": "^6.0|^7.0|^8.0|^9.0", + "illuminate/contracts": "^6.0|^7.0|^8.0|^9.0", + "illuminate/support": "^6.0|^7.0|^8.0|^9.0", + "php": "^7.2.5|^8.0", + "psy/psysh": "^0.10.4|^0.11.1", + "symfony/var-dumper": "^4.3.4|^5.0|^6.0" + }, + "require-dev": { + "mockery/mockery": "~1.3.3|^1.4.2", + "phpunit/phpunit": "^8.5.8|^9.3.3" + }, + "suggest": { + "illuminate/database": "The Illuminate Database package (^6.0|^7.0|^8.0|^9.0)." }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.x-dev" + "dev-master": "2.x-dev" }, "laravel": { "providers": [ - "Laravel\\Ui\\UiServiceProvider" + "Laravel\\Tinker\\TinkerServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Laravel\\Tinker\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Powerful REPL for the Laravel framework.", + "keywords": [ + "REPL", + "Tinker", + "laravel", + "psysh" + ], + "support": { + "issues": "https://github.com/laravel/tinker/issues", + "source": "https://github.com/laravel/tinker/tree/v2.7.2" + }, + "time": "2022-03-23T12:38:24+00:00" + }, + { + "name": "laravel/ui", + "version": "v3.4.5", + "source": { + "type": "git", + "url": "https://github.com/laravel/ui.git", + "reference": "f11d295de1508c5bb56206a620b00b6616de414c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/ui/zipball/f11d295de1508c5bb56206a620b00b6616de414c", + "reference": "f11d295de1508c5bb56206a620b00b6616de414c", + "shasum": "" + }, + "require": { + "illuminate/console": "^8.42|^9.0", + "illuminate/filesystem": "^8.42|^9.0", + "illuminate/support": "^8.82|^9.0", + "illuminate/validation": "^8.42|^9.0", + "php": "^7.3|^8.0" + }, + "require-dev": { + "orchestra/testbench": "^6.23|^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + }, + "laravel": { + "providers": [ + "Laravel\\Ui\\UiServiceProvider" ] } }, @@ -1633,37 +1910,37 @@ "ui" ], "support": { - "source": "https://github.com/laravel/ui/tree/v3.3.0" + "source": "https://github.com/laravel/ui/tree/v3.4.5" }, - "time": "2021-05-25T16:45:33+00:00" + "time": "2022-02-21T14:59:16+00:00" }, { "name": "lcobucci/clock", - "version": "2.0.0", + "version": "2.2.0", "source": { "type": "git", "url": "https://github.com/lcobucci/clock.git", - "reference": "353d83fe2e6ae95745b16b3d911813df6a05bfb3" + "reference": "fb533e093fd61321bfcbac08b131ce805fe183d3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/lcobucci/clock/zipball/353d83fe2e6ae95745b16b3d911813df6a05bfb3", - "reference": "353d83fe2e6ae95745b16b3d911813df6a05bfb3", + "url": "https://api.github.com/repos/lcobucci/clock/zipball/fb533e093fd61321bfcbac08b131ce805fe183d3", + "reference": "fb533e093fd61321bfcbac08b131ce805fe183d3", "shasum": "" }, "require": { - "php": "^7.4 || ^8.0" + "php": "^8.0", + "stella-maris/clock": "^0.1.4" }, "require-dev": { - "infection/infection": "^0.17", - "lcobucci/coding-standard": "^6.0", - "phpstan/extension-installer": "^1.0", + "infection/infection": "^0.26", + "lcobucci/coding-standard": "^8.0", + "phpstan/extension-installer": "^1.1", "phpstan/phpstan": "^0.12", "phpstan/phpstan-deprecation-rules": "^0.12", "phpstan/phpstan-phpunit": "^0.12", "phpstan/phpstan-strict-rules": "^0.12", - "phpunit/php-code-coverage": "9.1.4", - "phpunit/phpunit": "9.3.7" + "phpunit/phpunit": "^9.5" }, "type": "library", "autoload": { @@ -1684,7 +1961,7 @@ "description": "Yet another clock abstraction", "support": { "issues": "https://github.com/lcobucci/clock/issues", - "source": "https://github.com/lcobucci/clock/tree/2.0.x" + "source": "https://github.com/lcobucci/clock/tree/2.2.0" }, "funding": [ { @@ -1696,20 +1973,20 @@ "type": "patreon" } ], - "time": "2020-08-27T18:56:02+00:00" + "time": "2022-04-19T19:34:17+00:00" }, { "name": "lcobucci/jwt", - "version": "4.1.4", + "version": "4.1.5", "source": { "type": "git", "url": "https://github.com/lcobucci/jwt.git", - "reference": "71cf170102c8371ccd933fa4df6252086d144de6" + "reference": "fe2d89f2eaa7087af4aa166c6f480ef04e000582" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/lcobucci/jwt/zipball/71cf170102c8371ccd933fa4df6252086d144de6", - "reference": "71cf170102c8371ccd933fa4df6252086d144de6", + "url": "https://api.github.com/repos/lcobucci/jwt/zipball/fe2d89f2eaa7087af4aa166c6f480ef04e000582", + "reference": "fe2d89f2eaa7087af4aa166c6f480ef04e000582", "shasum": "" }, "require": { @@ -1725,7 +2002,7 @@ "infection/infection": "^0.21", "lcobucci/coding-standard": "^6.0", "mikey179/vfsstream": "^1.6.7", - "phpbench/phpbench": "^1.0@alpha", + "phpbench/phpbench": "^1.0", "phpstan/extension-installer": "^1.0", "phpstan/phpstan": "^0.12", "phpstan/phpstan-deprecation-rules": "^0.12", @@ -1758,7 +2035,7 @@ ], "support": { "issues": "https://github.com/lcobucci/jwt/issues", - "source": "https://github.com/lcobucci/jwt/tree/4.1.4" + "source": "https://github.com/lcobucci/jwt/tree/4.1.5" }, "funding": [ { @@ -1770,46 +2047,58 @@ "type": "patreon" } ], - "time": "2021-03-23T23:53:08+00:00" + "time": "2021-09-28T19:34:56+00:00" }, { "name": "league/commonmark", - "version": "1.6.4", + "version": "2.3.0", "source": { "type": "git", "url": "https://github.com/thephpleague/commonmark.git", - "reference": "c3c8b7217c52572fb42aaf84211abccf75a151b2" + "reference": "32a49eb2b38fe5e5c417ab748a45d0beaab97955" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/c3c8b7217c52572fb42aaf84211abccf75a151b2", - "reference": "c3c8b7217c52572fb42aaf84211abccf75a151b2", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/32a49eb2b38fe5e5c417ab748a45d0beaab97955", + "reference": "32a49eb2b38fe5e5c417ab748a45d0beaab97955", "shasum": "" }, "require": { "ext-mbstring": "*", - "php": "^7.1 || ^8.0" - }, - "conflict": { - "scrutinizer/ocular": "1.7.*" + "league/config": "^1.1.1", + "php": "^7.4 || ^8.0", + "psr/event-dispatcher": "^1.0", + "symfony/deprecation-contracts": "^2.1 || ^3.0", + "symfony/polyfill-php80": "^1.16" }, "require-dev": { - "cebe/markdown": "~1.0", - "commonmark/commonmark.js": "0.29.2", - "erusev/parsedown": "~1.0", + "cebe/markdown": "^1.0", + "commonmark/cmark": "0.30.0", + "commonmark/commonmark.js": "0.30.0", + "composer/package-versions-deprecated": "^1.8", + "embed/embed": "^4.4", + "erusev/parsedown": "^1.0", "ext-json": "*", "github/gfm": "0.29.0", - "michelf/php-markdown": "~1.4", - "mikehaertl/php-shellcommand": "^1.4", - "phpstan/phpstan": "^0.12.90", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.2", - "scrutinizer/ocular": "^1.5", - "symfony/finder": "^4.2" + "michelf/php-markdown": "^1.4", + "nyholm/psr7": "^1.5", + "phpstan/phpstan": "^0.12.88 || ^1.0.0", + "phpunit/phpunit": "^9.5.5", + "scrutinizer/ocular": "^1.8.1", + "symfony/finder": "^5.3", + "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0", + "unleashedtech/php-coding-standard": "^3.1", + "vimeo/psalm": "^4.7.3" + }, + "suggest": { + "symfony/yaml": "v2.3+ required if using the Front Matter extension" }, - "bin": [ - "bin/commonmark" - ], "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.4-dev" + } + }, "autoload": { "psr-4": { "League\\CommonMark\\": "src" @@ -1827,7 +2116,7 @@ "role": "Lead Developer" } ], - "description": "Highly-extensible PHP Markdown parser which fully supports the CommonMark spec and Github-Flavored Markdown (GFM)", + "description": "Highly-extensible PHP Markdown parser which fully supports the CommonMark spec and GitHub-Flavored Markdown (GFM)", "homepage": "https://commonmark.thephpleague.com", "keywords": [ "commonmark", @@ -1841,15 +2130,12 @@ ], "support": { "docs": "https://commonmark.thephpleague.com/", + "forum": "https://github.com/thephpleague/commonmark/discussions", "issues": "https://github.com/thephpleague/commonmark/issues", "rss": "https://github.com/thephpleague/commonmark/releases.atom", "source": "https://github.com/thephpleague/commonmark" }, "funding": [ - { - "url": "https://enjoy.gitstore.app/repositories/thephpleague/commonmark", - "type": "custom" - }, { "url": "https://www.colinodell.com/sponsor", "type": "custom" @@ -1862,29 +2148,107 @@ "url": "https://github.com/colinodell", "type": "github" }, - { - "url": "https://www.patreon.com/colinodell", - "type": "patreon" - }, { "url": "https://tidelift.com/funding/github/packagist/league/commonmark", "type": "tidelift" } ], - "time": "2021-06-19T20:08:14+00:00" + "time": "2022-04-07T22:37:05+00:00" + }, + { + "name": "league/config", + "version": "v1.1.1", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/config.git", + "reference": "a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/config/zipball/a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e", + "reference": "a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e", + "shasum": "" + }, + "require": { + "dflydev/dot-access-data": "^3.0.1", + "nette/schema": "^1.2", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^0.12.90", + "phpunit/phpunit": "^9.5.5", + "scrutinizer/ocular": "^1.8.1", + "unleashedtech/php-coding-standard": "^3.1", + "vimeo/psalm": "^4.7.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.2-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Config\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com", + "role": "Lead Developer" + } + ], + "description": "Define configuration arrays with strict schemas and access values with dot notation", + "homepage": "https://config.thephpleague.com", + "keywords": [ + "array", + "config", + "configuration", + "dot", + "dot-access", + "nested", + "schema" + ], + "support": { + "docs": "https://config.thephpleague.com/", + "issues": "https://github.com/thephpleague/config/issues", + "rss": "https://github.com/thephpleague/config/releases.atom", + "source": "https://github.com/thephpleague/config" + }, + "funding": [ + { + "url": "https://www.colinodell.com/sponsor", + "type": "custom" + }, + { + "url": "https://www.paypal.me/colinpodell/10.00", + "type": "custom" + }, + { + "url": "https://github.com/colinodell", + "type": "github" + } + ], + "time": "2021-08-14T12:15:32+00:00" }, { "name": "league/flysystem", - "version": "1.1.3", + "version": "1.1.9", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "9be3b16c877d477357c015cec057548cf9b2a14a" + "reference": "094defdb4a7001845300334e7c1ee2335925ef99" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/9be3b16c877d477357c015cec057548cf9b2a14a", - "reference": "9be3b16c877d477357c015cec057548cf9b2a14a", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/094defdb4a7001845300334e7c1ee2335925ef99", + "reference": "094defdb4a7001845300334e7c1ee2335925ef99", "shasum": "" }, "require": { @@ -1900,7 +2264,6 @@ "phpunit/phpunit": "^8.5.8" }, "suggest": { - "ext-fileinfo": "Required for MimeType", "ext-ftp": "Allows you to use FTP server storage", "ext-openssl": "Allows you to use FTPS server storage", "league/flysystem-aws-s3-v2": "Allows you to use S3 storage with AWS SDK v2", @@ -1958,7 +2321,7 @@ ], "support": { "issues": "https://github.com/thephpleague/flysystem/issues", - "source": "https://github.com/thephpleague/flysystem/tree/1.x" + "source": "https://github.com/thephpleague/flysystem/tree/1.1.9" }, "funding": [ { @@ -1966,7 +2329,7 @@ "type": "other" } ], - "time": "2020-08-23T07:39:11+00:00" + "time": "2021-12-09T09:40:50+00:00" }, { "name": "league/flysystem-aws-s3-v3", @@ -2076,28 +2439,30 @@ }, { "name": "league/fractal", - "version": "0.19.2", + "version": "0.20.1", "source": { "type": "git", "url": "https://github.com/thephpleague/fractal.git", - "reference": "06dc15f6ba38f2dde2f919d3095d13b571190a7c" + "reference": "8b9d39b67624db9195c06f9c1ffd0355151eaf62" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/fractal/zipball/06dc15f6ba38f2dde2f919d3095d13b571190a7c", - "reference": "06dc15f6ba38f2dde2f919d3095d13b571190a7c", + "url": "https://api.github.com/repos/thephpleague/fractal/zipball/8b9d39b67624db9195c06f9c1ffd0355151eaf62", + "reference": "8b9d39b67624db9195c06f9c1ffd0355151eaf62", "shasum": "" }, "require": { - "php": ">=5.4" + "php": ">=7.4" }, "require-dev": { "doctrine/orm": "^2.5", "illuminate/contracts": "~5.0", - "mockery/mockery": "~0.9", + "mockery/mockery": "^1.3", "pagerfanta/pagerfanta": "~1.0.0", - "phpunit/phpunit": "^4.8.35 || ^7.5", - "squizlabs/php_codesniffer": "~1.5|~2.0|~3.4", + "phpstan/phpstan": "^1.4", + "phpunit/phpunit": "^9.5", + "squizlabs/php_codesniffer": "~3.4", + "vimeo/psalm": "^4.22", "zendframework/zend-paginator": "~2.3" }, "suggest": { @@ -2108,7 +2473,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "0.13-dev" + "dev-master": "0.20.x-dev" } }, "autoload": { @@ -2138,22 +2503,22 @@ ], "support": { "issues": "https://github.com/thephpleague/fractal/issues", - "source": "https://github.com/thephpleague/fractal/tree/0.19.2" + "source": "https://github.com/thephpleague/fractal/tree/0.20.1" }, - "time": "2020-01-24T23:17:29+00:00" + "time": "2022-04-11T12:47:17+00:00" }, { "name": "league/mime-type-detection", - "version": "1.7.0", + "version": "1.11.0", "source": { "type": "git", "url": "https://github.com/thephpleague/mime-type-detection.git", - "reference": "3b9dff8aaf7323590c1d2e443db701eb1f9aa0d3" + "reference": "ff6248ea87a9f116e78edd6002e39e5128a0d4dd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/3b9dff8aaf7323590c1d2e443db701eb1f9aa0d3", - "reference": "3b9dff8aaf7323590c1d2e443db701eb1f9aa0d3", + "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/ff6248ea87a9f116e78edd6002e39e5128a0d4dd", + "reference": "ff6248ea87a9f116e78edd6002e39e5128a0d4dd", "shasum": "" }, "require": { @@ -2161,7 +2526,7 @@ "php": "^7.2 || ^8.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^2.18", + "friendsofphp/php-cs-fixer": "^3.2", "phpstan/phpstan": "^0.12.68", "phpunit/phpunit": "^8.5.8 || ^9.3" }, @@ -2184,7 +2549,7 @@ "description": "Mime-type detection for Flysystem", "support": { "issues": "https://github.com/thephpleague/mime-type-detection/issues", - "source": "https://github.com/thephpleague/mime-type-detection/tree/1.7.0" + "source": "https://github.com/thephpleague/mime-type-detection/tree/1.11.0" }, "funding": [ { @@ -2196,7 +2561,7 @@ "type": "tidelift" } ], - "time": "2021-01-18T20:58:21+00:00" + "time": "2022-04-17T13:12:02+00:00" }, { "name": "matriphe/iso-639", @@ -2248,24 +2613,24 @@ }, { "name": "monolog/monolog", - "version": "2.2.0", + "version": "2.5.0", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "1cb1cde8e8dd0f70cc0fe51354a59acad9302084" + "reference": "4192345e260f1d51b365536199744b987e160edc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/1cb1cde8e8dd0f70cc0fe51354a59acad9302084", - "reference": "1cb1cde8e8dd0f70cc0fe51354a59acad9302084", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/4192345e260f1d51b365536199744b987e160edc", + "reference": "4192345e260f1d51b365536199744b987e160edc", "shasum": "" }, "require": { "php": ">=7.2", - "psr/log": "^1.0.1" + "psr/log": "^1.0.1 || ^2.0 || ^3.0" }, "provide": { - "psr/log-implementation": "1.0.0" + "psr/log-implementation": "1.0.0 || 2.0.0 || 3.0.0" }, "require-dev": { "aws/aws-sdk-php": "^2.4.9 || ^3.0", @@ -2273,14 +2638,14 @@ "elasticsearch/elasticsearch": "^7", "graylog2/gelf-php": "^1.4.2", "mongodb/mongodb": "^1.8", - "php-amqplib/php-amqplib": "~2.4", + "php-amqplib/php-amqplib": "~2.4 || ^3", "php-console/php-console": "^3.1.3", "phpspec/prophecy": "^1.6.1", - "phpstan/phpstan": "^0.12.59", + "phpstan/phpstan": "^0.12.91", "phpunit/phpunit": "^8.5", "predis/predis": "^1.1", - "rollbar/rollbar": "^1.3", - "ruflin/elastica": ">=0.90 <7.0.1", + "rollbar/rollbar": "^1.3 || ^2 || ^3", + "ruflin/elastica": ">=0.90@dev", "swiftmailer/swiftmailer": "^5.3|^6.0" }, "suggest": { @@ -2288,8 +2653,11 @@ "doctrine/couchdb": "Allow sending log messages to a CouchDB server", "elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client", "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-curl": "Required to send log messages using the IFTTTHandler, the LogglyHandler, the SendGridHandler, the SlackWebhookHandler or the TelegramBotHandler", "ext-mbstring": "Allow to work properly with unicode symbols", "ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)", + "ext-openssl": "Required to send log messages using SSL", + "ext-sockets": "Allow sending log messages to a Syslog server (via UDP driver)", "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)", "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", @@ -2328,7 +2696,7 @@ ], "support": { "issues": "https://github.com/Seldaek/monolog/issues", - "source": "https://github.com/Seldaek/monolog/tree/2.2.0" + "source": "https://github.com/Seldaek/monolog/tree/2.5.0" }, "funding": [ { @@ -2340,7 +2708,7 @@ "type": "tidelift" } ], - "time": "2020-12-14T13:15:25+00:00" + "time": "2022-04-08T15:43:54+00:00" }, { "name": "mtdowling/jmespath.php", @@ -2374,12 +2742,12 @@ } }, "autoload": { - "psr-4": { - "JmesPath\\": "src/" - }, "files": [ "src/JmesPath.php" - ] + ], + "psr-4": { + "JmesPath\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -2405,32 +2773,35 @@ }, { "name": "nesbot/carbon", - "version": "2.49.0", + "version": "2.58.0", "source": { "type": "git", "url": "https://github.com/briannesbitt/Carbon.git", - "reference": "93d9db91c0235c486875d22f1e08b50bdf3e6eee" + "reference": "97a34af22bde8d0ac20ab34b29d7bfe360902055" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/93d9db91c0235c486875d22f1e08b50bdf3e6eee", - "reference": "93d9db91c0235c486875d22f1e08b50bdf3e6eee", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/97a34af22bde8d0ac20ab34b29d7bfe360902055", + "reference": "97a34af22bde8d0ac20ab34b29d7bfe360902055", "shasum": "" }, "require": { "ext-json": "*", "php": "^7.1.8 || ^8.0", "symfony/polyfill-mbstring": "^1.0", - "symfony/translation": "^3.4 || ^4.0 || ^5.0" + "symfony/polyfill-php80": "^1.16", + "symfony/translation": "^3.4 || ^4.0 || ^5.0 || ^6.0" }, "require-dev": { + "doctrine/dbal": "^2.0 || ^3.0", "doctrine/orm": "^2.7", - "friendsofphp/php-cs-fixer": "^2.14 || ^3.0", + "friendsofphp/php-cs-fixer": "^3.0", "kylekatarnls/multi-tester": "^2.0", "phpmd/phpmd": "^2.9", "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^0.12.54", - "phpunit/phpunit": "^7.5.20 || ^8.5.14", + "phpstan/phpstan": "^0.12.54 || ^1.0", + "phpunit/php-file-iterator": "^2.0.5", + "phpunit/phpunit": "^7.5.20 || ^8.5.23", "squizlabs/php_codesniffer": "^3.4" }, "bin": [ @@ -2439,8 +2810,8 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.x-dev", - "dev-3.x": "3.x-dev" + "dev-3.x": "3.x-dev", + "dev-master": "2.x-dev" }, "laravel": { "providers": [ @@ -2466,21 +2837,22 @@ { "name": "Brian Nesbitt", "email": "brian@nesbot.com", - "homepage": "http://nesbot.com" + "homepage": "https://markido.com" }, { "name": "kylekatarnls", - "homepage": "http://github.com/kylekatarnls" + "homepage": "https://github.com/kylekatarnls" } ], "description": "An API extension for DateTime that supports 281 different languages.", - "homepage": "http://carbon.nesbot.com", + "homepage": "https://carbon.nesbot.com", "keywords": [ "date", "datetime", "time" ], "support": { + "docs": "https://carbon.nesbot.com/docs", "issues": "https://github.com/briannesbitt/Carbon/issues", "source": "https://github.com/briannesbitt/Carbon" }, @@ -2494,20 +2866,167 @@ "type": "tidelift" } ], - "time": "2021-06-02T07:31:40+00:00" + "time": "2022-04-25T19:31:17+00:00" + }, + { + "name": "nette/schema", + "version": "v1.2.2", + "source": { + "type": "git", + "url": "https://github.com/nette/schema.git", + "reference": "9a39cef03a5b34c7de64f551538cbba05c2be5df" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/schema/zipball/9a39cef03a5b34c7de64f551538cbba05c2be5df", + "reference": "9a39cef03a5b34c7de64f551538cbba05c2be5df", + "shasum": "" + }, + "require": { + "nette/utils": "^2.5.7 || ^3.1.5 || ^4.0", + "php": ">=7.1 <8.2" + }, + "require-dev": { + "nette/tester": "^2.3 || ^2.4", + "phpstan/phpstan-nette": "^0.12", + "tracy/tracy": "^2.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "📐 Nette Schema: validating data structures against a given Schema.", + "homepage": "https://nette.org", + "keywords": [ + "config", + "nette" + ], + "support": { + "issues": "https://github.com/nette/schema/issues", + "source": "https://github.com/nette/schema/tree/v1.2.2" + }, + "time": "2021-10-15T11:40:02+00:00" + }, + { + "name": "nette/utils", + "version": "v3.2.7", + "source": { + "type": "git", + "url": "https://github.com/nette/utils.git", + "reference": "0af4e3de4df9f1543534beab255ccf459e7a2c99" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/utils/zipball/0af4e3de4df9f1543534beab255ccf459e7a2c99", + "reference": "0af4e3de4df9f1543534beab255ccf459e7a2c99", + "shasum": "" + }, + "require": { + "php": ">=7.2 <8.2" + }, + "conflict": { + "nette/di": "<3.0.6" + }, + "require-dev": { + "nette/tester": "~2.0", + "phpstan/phpstan": "^1.0", + "tracy/tracy": "^2.3" + }, + "suggest": { + "ext-gd": "to use Image", + "ext-iconv": "to use Strings::webalize(), toAscii(), chr() and reverse()", + "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()", + "ext-json": "to use Nette\\Utils\\Json", + "ext-mbstring": "to use Strings::lower() etc...", + "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()", + "ext-xml": "to use Strings::length() etc. when mbstring is not available" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.", + "homepage": "https://nette.org", + "keywords": [ + "array", + "core", + "datetime", + "images", + "json", + "nette", + "paginator", + "password", + "slugify", + "string", + "unicode", + "utf-8", + "utility", + "validation" + ], + "support": { + "issues": "https://github.com/nette/utils/issues", + "source": "https://github.com/nette/utils/tree/v3.2.7" + }, + "time": "2022-01-24T11:29:14+00:00" }, { "name": "nikic/php-parser", - "version": "v4.10.5", + "version": "v4.13.2", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "4432ba399e47c66624bc73c8c0f811e5c109576f" + "reference": "210577fe3cf7badcc5814d99455df46564f3c077" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/4432ba399e47c66624bc73c8c0f811e5c109576f", - "reference": "4432ba399e47c66624bc73c8c0f811e5c109576f", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/210577fe3cf7badcc5814d99455df46564f3c077", + "reference": "210577fe3cf7badcc5814d99455df46564f3c077", "shasum": "" }, "require": { @@ -2548,22 +3067,22 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.10.5" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.13.2" }, - "time": "2021-05-03T19:11:20+00:00" + "time": "2021-11-30T19:35:32+00:00" }, { "name": "opis/closure", - "version": "3.6.2", + "version": "3.6.3", "source": { "type": "git", "url": "https://github.com/opis/closure.git", - "reference": "06e2ebd25f2869e54a306dda991f7db58066f7f6" + "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opis/closure/zipball/06e2ebd25f2869e54a306dda991f7db58066f7f6", - "reference": "06e2ebd25f2869e54a306dda991f7db58066f7f6", + "url": "https://api.github.com/repos/opis/closure/zipball/3d81e4309d2a927abbe66df935f4bb60082805ad", + "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad", "shasum": "" }, "require": { @@ -2580,12 +3099,12 @@ } }, "autoload": { - "psr-4": { - "Opis\\Closure\\": "src/" - }, "files": [ "functions.php" - ] + ], + "psr-4": { + "Opis\\Closure\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -2613,22 +3132,22 @@ ], "support": { "issues": "https://github.com/opis/closure/issues", - "source": "https://github.com/opis/closure/tree/3.6.2" + "source": "https://github.com/opis/closure/tree/3.6.3" }, - "time": "2021-04-09T13:42:10+00:00" + "time": "2022-01-27T09:35:39+00:00" }, { "name": "paragonie/constant_time_encoding", - "version": "v2.4.0", + "version": "v2.5.0", "source": { "type": "git", "url": "https://github.com/paragonie/constant_time_encoding.git", - "reference": "f34c2b11eb9d2c9318e13540a1dbc2a3afbd939c" + "reference": "9229e15f2e6ba772f0c55dd6986c563b937170a8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/f34c2b11eb9d2c9318e13540a1dbc2a3afbd939c", - "reference": "f34c2b11eb9d2c9318e13540a1dbc2a3afbd939c", + "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/9229e15f2e6ba772f0c55dd6986c563b937170a8", + "reference": "9229e15f2e6ba772f0c55dd6986c563b937170a8", "shasum": "" }, "require": { @@ -2682,7 +3201,7 @@ "issues": "https://github.com/paragonie/constant_time_encoding/issues", "source": "https://github.com/paragonie/constant_time_encoding" }, - "time": "2020-12-06T15:14:20+00:00" + "time": "2022-01-17T05:32:27+00:00" }, { "name": "paragonie/random_compat", @@ -2736,29 +3255,29 @@ }, { "name": "phpoption/phpoption", - "version": "1.7.5", + "version": "1.8.1", "source": { "type": "git", "url": "https://github.com/schmittjoh/php-option.git", - "reference": "994ecccd8f3283ecf5ac33254543eb0ac946d525" + "reference": "eab7a0df01fe2344d172bff4cd6dbd3f8b84ad15" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/994ecccd8f3283ecf5ac33254543eb0ac946d525", - "reference": "994ecccd8f3283ecf5ac33254543eb0ac946d525", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/eab7a0df01fe2344d172bff4cd6dbd3f8b84ad15", + "reference": "eab7a0df01fe2344d172bff4cd6dbd3f8b84ad15", "shasum": "" }, "require": { - "php": "^5.5.9 || ^7.0 || ^8.0" + "php": "^7.0 || ^8.0" }, "require-dev": { "bamarni/composer-bin-plugin": "^1.4.1", - "phpunit/phpunit": "^4.8.35 || ^5.7.27 || ^6.5.6 || ^7.0 || ^8.0 || ^9.0" + "phpunit/phpunit": "^6.5.14 || ^7.5.20 || ^8.5.19 || ^9.5.8" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.7-dev" + "dev-master": "1.8-dev" } }, "autoload": { @@ -2773,11 +3292,13 @@ "authors": [ { "name": "Johannes M. Schmitt", - "email": "schmittjoh@gmail.com" + "email": "schmittjoh@gmail.com", + "homepage": "https://github.com/schmittjoh" }, { "name": "Graham Campbell", - "email": "graham@alt-three.com" + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" } ], "description": "Option Type for PHP", @@ -2789,7 +3310,7 @@ ], "support": { "issues": "https://github.com/schmittjoh/php-option/issues", - "source": "https://github.com/schmittjoh/php-option/tree/1.7.5" + "source": "https://github.com/schmittjoh/php-option/tree/1.8.1" }, "funding": [ { @@ -2801,7 +3322,7 @@ "type": "tidelift" } ], - "time": "2020-07-20T17:29:33+00:00" + "time": "2021-12-04T23:24:31+00:00" }, { "name": "pragmarx/google2fa", @@ -2865,16 +3386,16 @@ }, { "name": "predis/predis", - "version": "v1.1.7", + "version": "v1.1.10", "source": { "type": "git", "url": "https://github.com/predis/predis.git", - "reference": "b240daa106d4e02f0c5b7079b41e31ddf66fddf8" + "reference": "a2fb02d738bedadcffdbb07efa3a5e7bd57f8d6e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/predis/predis/zipball/b240daa106d4e02f0c5b7079b41e31ddf66fddf8", - "reference": "b240daa106d4e02f0c5b7079b41e31ddf66fddf8", + "url": "https://api.github.com/repos/predis/predis/zipball/a2fb02d738bedadcffdbb07efa3a5e7bd57f8d6e", + "reference": "a2fb02d738bedadcffdbb07efa3a5e7bd57f8d6e", "shasum": "" }, "require": { @@ -2919,7 +3440,7 @@ ], "support": { "issues": "https://github.com/predis/predis/issues", - "source": "https://github.com/predis/predis/tree/v1.1.7" + "source": "https://github.com/predis/predis/tree/v1.1.10" }, "funding": [ { @@ -2927,7 +3448,7 @@ "type": "github" } ], - "time": "2021-04-04T19:34:46+00:00" + "time": "2022-01-05T17:46:08+00:00" }, { "name": "prologue/alerts", @@ -3050,20 +3571,20 @@ }, { "name": "psr/container", - "version": "1.1.1", + "version": "1.1.2", "source": { "type": "git", "url": "https://github.com/php-fig/container.git", - "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf" + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/8622567409010282b7aeebe4bb841fe98b58dcaf", - "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf", + "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", "shasum": "" }, "require": { - "php": ">=7.2.0" + "php": ">=7.4.0" }, "type": "library", "autoload": { @@ -3092,9 +3613,9 @@ ], "support": { "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/1.1.1" + "source": "https://github.com/php-fig/container/tree/1.1.2" }, - "time": "2021-03-05T17:36:06+00:00" + "time": "2021-11-05T16:50:12+00:00" }, { "name": "psr/event-dispatcher", @@ -3199,21 +3720,22 @@ "time": "2020-06-29T06:28:15+00:00" }, { - "name": "psr/http-message", + "name": "psr/http-factory", "version": "1.0.1", "source": { "type": "git", - "url": "https://github.com/php-fig/http-message.git", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": ">=7.0.0", + "psr/http-message": "^1.0" }, "type": "library", "extra": { @@ -3236,8 +3758,62 @@ "homepage": "http://www.php-fig.org/" } ], - "description": "Common interface for HTTP messages", - "homepage": "https://github.com/php-fig/http-message", + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/master" + }, + "time": "2019-04-30T12:38:16+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", "keywords": [ "http", "http-message", @@ -3253,30 +3829,30 @@ }, { "name": "psr/log", - "version": "1.1.4", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "d49695b909c3b7628b6289db5479a1c204601f11" + "reference": "ef29f6d262798707a9edd554e2b82517ef3a9376" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", - "reference": "d49695b909c3b7628b6289db5479a1c204601f11", + "url": "https://api.github.com/repos/php-fig/log/zipball/ef29f6d262798707a9edd554e2b82517ef3a9376", + "reference": "ef29f6d262798707a9edd554e2b82517ef3a9376", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": ">=8.0.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1.x-dev" + "dev-master": "2.0.x-dev" } }, "autoload": { "psr-4": { - "Psr\\Log\\": "Psr/Log/" + "Psr\\Log\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -3297,9 +3873,9 @@ "psr-3" ], "support": { - "source": "https://github.com/php-fig/log/tree/1.1.4" + "source": "https://github.com/php-fig/log/tree/2.0.0" }, - "time": "2021-05-03T11:20:27+00:00" + "time": "2021-07-14T16:41:46+00:00" }, { "name": "psr/simple-cache", @@ -3354,29 +3930,32 @@ }, { "name": "psy/psysh", - "version": "v0.10.8", + "version": "v0.11.2", "source": { "type": "git", "url": "https://github.com/bobthecow/psysh.git", - "reference": "e4573f47750dd6c92dca5aee543fa77513cbd8d3" + "reference": "7f7da640d68b9c9fec819caae7c744a213df6514" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bobthecow/psysh/zipball/e4573f47750dd6c92dca5aee543fa77513cbd8d3", - "reference": "e4573f47750dd6c92dca5aee543fa77513cbd8d3", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/7f7da640d68b9c9fec819caae7c744a213df6514", + "reference": "7f7da640d68b9c9fec819caae7c744a213df6514", "shasum": "" }, "require": { "ext-json": "*", "ext-tokenizer": "*", - "nikic/php-parser": "~4.0|~3.0|~2.0|~1.3", - "php": "^8.0 || ^7.0 || ^5.5.9", - "symfony/console": "~5.0|~4.0|~3.0|^2.4.2|~2.3.10", - "symfony/var-dumper": "~5.0|~4.0|~3.0|~2.7" + "nikic/php-parser": "^4.0 || ^3.1", + "php": "^8.0 || ^7.0.8", + "symfony/console": "^6.0 || ^5.0 || ^4.0 || ^3.4", + "symfony/var-dumper": "^6.0 || ^5.0 || ^4.0 || ^3.4" + }, + "conflict": { + "symfony/console": "4.4.37 || 5.3.14 || 5.3.15 || 5.4.3 || 5.4.4 || 6.0.3 || 6.0.4" }, "require-dev": { "bamarni/composer-bin-plugin": "^1.2", - "hoa/console": "3.17.*" + "hoa/console": "3.17.05.02" }, "suggest": { "ext-pcntl": "Enabling the PCNTL extension makes PsySH a lot happier :)", @@ -3391,7 +3970,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "0.10.x-dev" + "dev-main": "0.11.x-dev" } }, "autoload": { @@ -3423,9 +4002,9 @@ ], "support": { "issues": "https://github.com/bobthecow/psysh/issues", - "source": "https://github.com/bobthecow/psysh/tree/v0.10.8" + "source": "https://github.com/bobthecow/psysh/tree/v0.11.2" }, - "time": "2021-04-10T16:23:39+00:00" + "time": "2022-02-28T15:28:54+00:00" }, { "name": "ralouphie/getallheaders", @@ -3473,20 +4052,21 @@ }, { "name": "ramsey/collection", - "version": "1.1.3", + "version": "1.2.2", "source": { "type": "git", "url": "https://github.com/ramsey/collection.git", - "reference": "28a5c4ab2f5111db6a60b2b4ec84057e0f43b9c1" + "reference": "cccc74ee5e328031b15640b51056ee8d3bb66c0a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/collection/zipball/28a5c4ab2f5111db6a60b2b4ec84057e0f43b9c1", - "reference": "28a5c4ab2f5111db6a60b2b4ec84057e0f43b9c1", + "url": "https://api.github.com/repos/ramsey/collection/zipball/cccc74ee5e328031b15640b51056ee8d3bb66c0a", + "reference": "cccc74ee5e328031b15640b51056ee8d3bb66c0a", "shasum": "" }, "require": { - "php": "^7.2 || ^8" + "php": "^7.3 || ^8", + "symfony/polyfill-php81": "^1.23" }, "require-dev": { "captainhook/captainhook": "^5.3", @@ -3496,6 +4076,7 @@ "hamcrest/hamcrest-php": "^2", "jangregor/phpstan-prophecy": "^0.8", "mockery/mockery": "^1.3", + "phpspec/prophecy-phpunit": "^2.0", "phpstan/extension-installer": "^1", "phpstan/phpstan": "^0.12.32", "phpstan/phpstan-mockery": "^0.12.5", @@ -3523,7 +4104,7 @@ "homepage": "https://benramsey.com" } ], - "description": "A PHP 7.2+ library for representing and manipulating collections.", + "description": "A PHP library for representing and manipulating collections.", "keywords": [ "array", "collection", @@ -3534,7 +4115,7 @@ ], "support": { "issues": "https://github.com/ramsey/collection/issues", - "source": "https://github.com/ramsey/collection/tree/1.1.3" + "source": "https://github.com/ramsey/collection/tree/1.2.2" }, "funding": [ { @@ -3546,53 +4127,53 @@ "type": "tidelift" } ], - "time": "2021-01-21T17:40:04+00:00" + "time": "2021-10-10T03:01:02+00:00" }, { "name": "ramsey/uuid", - "version": "4.1.1", + "version": "4.3.1", "source": { "type": "git", "url": "https://github.com/ramsey/uuid.git", - "reference": "cd4032040a750077205918c86049aa0f43d22947" + "reference": "8505afd4fea63b81a85d3b7b53ac3cb8dc347c28" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/cd4032040a750077205918c86049aa0f43d22947", - "reference": "cd4032040a750077205918c86049aa0f43d22947", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/8505afd4fea63b81a85d3b7b53ac3cb8dc347c28", + "reference": "8505afd4fea63b81a85d3b7b53ac3cb8dc347c28", "shasum": "" }, "require": { "brick/math": "^0.8 || ^0.9", + "ext-ctype": "*", "ext-json": "*", - "php": "^7.2 || ^8", - "ramsey/collection": "^1.0", - "symfony/polyfill-ctype": "^1.8" + "php": "^8.0", + "ramsey/collection": "^1.0" }, "replace": { "rhumsaa/uuid": "self.version" }, "require-dev": { - "codeception/aspect-mock": "^3", - "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7.0", + "captainhook/captainhook": "^5.10", + "captainhook/plugin-composer": "^5.3", + "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", "doctrine/annotations": "^1.8", - "goaop/framework": "^2", + "ergebnis/composer-normalize": "^2.15", "mockery/mockery": "^1.3", "moontoast/math": "^1.1", "paragonie/random-lib": "^2", + "php-mock/php-mock": "^2.2", "php-mock/php-mock-mockery": "^1.3", - "php-mock/php-mock-phpunit": "^2.5", "php-parallel-lint/php-parallel-lint": "^1.1", - "phpbench/phpbench": "^0.17.1", + "phpbench/phpbench": "^1.0", "phpstan/extension-installer": "^1.0", "phpstan/phpstan": "^0.12", "phpstan/phpstan-mockery": "^0.12", "phpstan/phpstan-phpunit": "^0.12", - "phpunit/phpunit": "^8.5", - "psy/psysh": "^0.10.0", - "slevomat/coding-standard": "^6.0", + "phpunit/phpunit": "^8.5 || ^9", + "slevomat/coding-standard": "^7.0", "squizlabs/php_codesniffer": "^3.5", - "vimeo/psalm": "3.9.4" + "vimeo/psalm": "^4.9" }, "suggest": { "ext-bcmath": "Enables faster math with arbitrary-precision integers using BCMath.", @@ -3604,24 +4185,23 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "4.x-dev" + "captainhook": { + "force-install": true } }, "autoload": { - "psr-4": { - "Ramsey\\Uuid\\": "src/" - }, "files": [ "src/functions.php" - ] + ], + "psr-4": { + "Ramsey\\Uuid\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "description": "A PHP library for generating and working with universally unique identifiers (UUIDs).", - "homepage": "https://github.com/ramsey/uuid", "keywords": [ "guid", "identifier", @@ -3629,16 +4209,19 @@ ], "support": { "issues": "https://github.com/ramsey/uuid/issues", - "rss": "https://github.com/ramsey/uuid/releases.atom", - "source": "https://github.com/ramsey/uuid" + "source": "https://github.com/ramsey/uuid/tree/4.3.1" }, "funding": [ { "url": "https://github.com/ramsey", "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/ramsey/uuid", + "type": "tidelift" } ], - "time": "2020-08-18T17:17:46+00:00" + "time": "2022-03-27T21:42:02+00:00" }, { "name": "s1lentium/iptools", @@ -3697,21 +4280,21 @@ }, { "name": "spatie/fractalistic", - "version": "2.9.1", + "version": "2.9.5", "source": { "type": "git", "url": "https://github.com/spatie/fractalistic.git", - "reference": "c4fc2b223a8f7321ece449abc2c59cc5baf470fd" + "reference": "6f12686a03d035f4558d166989c62aa93bde2151" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/fractalistic/zipball/c4fc2b223a8f7321ece449abc2c59cc5baf470fd", - "reference": "c4fc2b223a8f7321ece449abc2c59cc5baf470fd", + "url": "https://api.github.com/repos/spatie/fractalistic/zipball/6f12686a03d035f4558d166989c62aa93bde2151", + "reference": "6f12686a03d035f4558d166989c62aa93bde2151", "shasum": "" }, "require": { - "league/fractal": "^0.19.0", - "php": "^7.3|^8.0" + "league/fractal": "^0.20.1", + "php": "^7.4|^8.0" }, "require-dev": { "illuminate/pagination": "~5.3.0|~5.4.0", @@ -3746,7 +4329,7 @@ ], "support": { "issues": "https://github.com/spatie/fractalistic/issues", - "source": "https://github.com/spatie/fractalistic/tree/2.9.1" + "source": "https://github.com/spatie/fractalistic/tree/2.9.5" }, "funding": [ { @@ -3754,7 +4337,7 @@ "type": "github" } ], - "time": "2020-11-12T18:19:10+00:00" + "time": "2022-04-21T12:26:22+00:00" }, { "name": "spatie/laravel-fractal", @@ -3792,12 +4375,12 @@ } }, "autoload": { - "psr-4": { - "Spatie\\Fractal\\": "src" - }, "files": [ "src/helpers.php" - ] + ], + "psr-4": { + "Spatie\\Fractal\\": "src" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -3836,22 +4419,22 @@ }, { "name": "spatie/laravel-query-builder", - "version": "3.4.1", + "version": "3.6.2", "source": { "type": "git", "url": "https://github.com/spatie/laravel-query-builder.git", - "reference": "6c09f1f9d6c988bf2e1220be072c7bd1ac958cc9" + "reference": "3768381e9f2f80b01f0088eca49f061c269e0e8b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-query-builder/zipball/6c09f1f9d6c988bf2e1220be072c7bd1ac958cc9", - "reference": "6c09f1f9d6c988bf2e1220be072c7bd1ac958cc9", + "url": "https://api.github.com/repos/spatie/laravel-query-builder/zipball/3768381e9f2f80b01f0088eca49f061c269e0e8b", + "reference": "3768381e9f2f80b01f0088eca49f061c269e0e8b", "shasum": "" }, "require": { "illuminate/database": "^6.20.13|^7.30.4|^8.22.2", - "illuminate/http": "^6.20.13|7.30.4|^8.22.2", - "illuminate/support": "^6.20.13|7.30.4|^8.22.2", + "illuminate/http": "^6.20.13|^7.30.4|^8.22.2", + "illuminate/support": "^6.20.13|^7.30.4|^8.22.2", "php": "^7.3|^8.0" }, "require-dev": { @@ -3902,20 +4485,20 @@ "type": "custom" } ], - "time": "2021-05-24T10:13:32+00:00" + "time": "2022-01-09T15:39:13+00:00" }, { "name": "staudenmeir/belongs-to-through", - "version": "v2.11.1", + "version": "v2.11.2", "source": { "type": "git", "url": "https://github.com/staudenmeir/belongs-to-through.git", - "reference": "d300afa1045e6541b79af2291336312613b5cd02" + "reference": "32d03527163a3edd7f88e4b74b03575e4bdb5db7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/staudenmeir/belongs-to-through/zipball/d300afa1045e6541b79af2291336312613b5cd02", - "reference": "d300afa1045e6541b79af2291336312613b5cd02", + "url": "https://api.github.com/repos/staudenmeir/belongs-to-through/zipball/32d03527163a3edd7f88e4b74b03575e4bdb5db7", + "reference": "32d03527163a3edd7f88e4b74b03575e4bdb5db7", "shasum": "" }, "require": { @@ -3923,7 +4506,8 @@ "php": "^7.3|^8.0" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^9.3", + "scrutinizer/ocular": "^1.8" }, "type": "library", "autoload": { @@ -3948,7 +4532,7 @@ "description": "Laravel Eloquent BelongsToThrough relationships", "support": { "issues": "https://github.com/staudenmeir/belongs-to-through/issues", - "source": "https://github.com/staudenmeir/belongs-to-through/tree/v2.11.1" + "source": "https://github.com/staudenmeir/belongs-to-through/tree/v2.11.2" }, "funding": [ { @@ -3956,20 +4540,67 @@ "type": "custom" } ], - "time": "2020-11-16T19:39:09+00:00" + "time": "2021-08-19T18:23:06+00:00" + }, + { + "name": "stella-maris/clock", + "version": "0.1.4", + "source": { + "type": "git", + "url": "https://gitlab.com/stella-maris/clock.git", + "reference": "8a0a967896df4c63417385dc69328a0aec84d9cf" + }, + "dist": { + "type": "zip", + "url": "https://gitlab.com/api/v4/projects/stella-maris%2Fclock/repository/archive.zip?sha=8a0a967896df4c63417385dc69328a0aec84d9cf", + "reference": "8a0a967896df4c63417385dc69328a0aec84d9cf", + "shasum": "" + }, + "require": { + "php": "^7.0|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "StellaMaris\\Clock\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Andreas Heigl", + "role": "Maintainer" + } + ], + "description": "A pre-release of the proposed PSR-20 Clock-Interface", + "homepage": "https://gitlab.com/stella-maris/clock", + "keywords": [ + "clock", + "datetime", + "point in time", + "psr20" + ], + "support": { + "issues": "https://gitlab.com/stella-maris/clock/-/issues", + "source": "https://gitlab.com/stella-maris/clock/-/tree/0.1.4" + }, + "time": "2022-04-17T14:12:26+00:00" }, { "name": "swiftmailer/swiftmailer", - "version": "v6.2.7", + "version": "v6.3.0", "source": { "type": "git", "url": "https://github.com/swiftmailer/swiftmailer.git", - "reference": "15f7faf8508e04471f666633addacf54c0ab5933" + "reference": "8a5d5072dca8f48460fce2f4131fcc495eec654c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/15f7faf8508e04471f666633addacf54c0ab5933", - "reference": "15f7faf8508e04471f666633addacf54c0ab5933", + "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/8a5d5072dca8f48460fce2f4131fcc495eec654c", + "reference": "8a5d5072dca8f48460fce2f4131fcc495eec654c", "shasum": "" }, "require": { @@ -3981,7 +4612,7 @@ }, "require-dev": { "mockery/mockery": "^1.0", - "symfony/phpunit-bridge": "^4.4|^5.0" + "symfony/phpunit-bridge": "^4.4|^5.4" }, "suggest": { "ext-intl": "Needed to support internationalized email addresses" @@ -4019,7 +4650,7 @@ ], "support": { "issues": "https://github.com/swiftmailer/swiftmailer/issues", - "source": "https://github.com/swiftmailer/swiftmailer/tree/v6.2.7" + "source": "https://github.com/swiftmailer/swiftmailer/tree/v6.3.0" }, "funding": [ { @@ -4031,32 +4662,34 @@ "type": "tidelift" } ], - "time": "2021-03-09T12:30:35+00:00" + "abandoned": "symfony/mailer", + "time": "2021-10-18T15:26:12+00:00" }, { "name": "symfony/console", - "version": "v5.3.2", + "version": "v5.4.8", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "649730483885ff2ca99ca0560ef0e5f6b03f2ac1" + "reference": "ffe3aed36c4d60da2cf1b0a1cee6b8f2e5fa881b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/649730483885ff2ca99ca0560ef0e5f6b03f2ac1", - "reference": "649730483885ff2ca99ca0560ef0e5f6b03f2ac1", + "url": "https://api.github.com/repos/symfony/console/zipball/ffe3aed36c4d60da2cf1b0a1cee6b8f2e5fa881b", + "reference": "ffe3aed36c4d60da2cf1b0a1cee6b8f2e5fa881b", "shasum": "" }, "require": { "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1", + "symfony/deprecation-contracts": "^2.1|^3", "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php73": "^1.8", - "symfony/polyfill-php80": "^1.15", - "symfony/service-contracts": "^1.1|^2", - "symfony/string": "^5.1" + "symfony/polyfill-php73": "^1.9", + "symfony/polyfill-php80": "^1.16", + "symfony/service-contracts": "^1.1|^2|^3", + "symfony/string": "^5.1|^6.0" }, "conflict": { + "psr/log": ">=3", "symfony/dependency-injection": "<4.4", "symfony/dotenv": "<5.1", "symfony/event-dispatcher": "<4.4", @@ -4064,16 +4697,16 @@ "symfony/process": "<4.4" }, "provide": { - "psr/log-implementation": "1.0" + "psr/log-implementation": "1.0|2.0" }, "require-dev": { - "psr/log": "~1.0", - "symfony/config": "^4.4|^5.0", - "symfony/dependency-injection": "^4.4|^5.0", - "symfony/event-dispatcher": "^4.4|^5.0", - "symfony/lock": "^4.4|^5.0", - "symfony/process": "^4.4|^5.0", - "symfony/var-dumper": "^4.4|^5.0" + "psr/log": "^1|^2", + "symfony/config": "^4.4|^5.0|^6.0", + "symfony/dependency-injection": "^4.4|^5.0|^6.0", + "symfony/event-dispatcher": "^4.4|^5.0|^6.0", + "symfony/lock": "^4.4|^5.0|^6.0", + "symfony/process": "^4.4|^5.0|^6.0", + "symfony/var-dumper": "^4.4|^5.0|^6.0" }, "suggest": { "psr/log": "For using the console logger", @@ -4113,7 +4746,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.3.2" + "source": "https://github.com/symfony/console/tree/v5.4.8" }, "funding": [ { @@ -4129,24 +4762,24 @@ "type": "tidelift" } ], - "time": "2021-06-12T09:42:48+00:00" + "time": "2022-04-12T16:02:29+00:00" }, { "name": "symfony/css-selector", - "version": "v5.3.0", + "version": "v6.0.3", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "fcd0b29a7a0b1bb5bfbedc6231583d77fea04814" + "reference": "1955d595c12c111629cc814d3f2a2ff13580508a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/fcd0b29a7a0b1bb5bfbedc6231583d77fea04814", - "reference": "fcd0b29a7a0b1bb5bfbedc6231583d77fea04814", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/1955d595c12c111629cc814d3f2a2ff13580508a", + "reference": "1955d595c12c111629cc814d3f2a2ff13580508a", "shasum": "" }, "require": { - "php": ">=7.2.5" + "php": ">=8.0.2" }, "type": "library", "autoload": { @@ -4178,7 +4811,7 @@ "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/css-selector/tree/v5.3.0" + "source": "https://github.com/symfony/css-selector/tree/v6.0.3" }, "funding": [ { @@ -4194,29 +4827,29 @@ "type": "tidelift" } ], - "time": "2021-05-26T17:40:38+00:00" + "time": "2022-01-02T09:55:41+00:00" }, { "name": "symfony/deprecation-contracts", - "version": "v2.4.0", + "version": "v3.0.1", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "5f38c8804a9e97d23e0c8d63341088cd8a22d627" + "reference": "26954b3d62a6c5fd0ea8a2a00c0353a14978d05c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/5f38c8804a9e97d23e0c8d63341088cd8a22d627", - "reference": "5f38c8804a9e97d23e0c8d63341088cd8a22d627", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/26954b3d62a6c5fd0ea8a2a00c0353a14978d05c", + "reference": "26954b3d62a6c5fd0ea8a2a00c0353a14978d05c", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=8.0.2" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "2.4-dev" + "dev-main": "3.0-dev" }, "thanks": { "name": "symfony/contracts", @@ -4245,7 +4878,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v2.4.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.0.1" }, "funding": [ { @@ -4261,33 +4894,35 @@ "type": "tidelift" } ], - "time": "2021-03-23T23:28:01+00:00" + "time": "2022-01-02T09:55:41+00:00" }, { "name": "symfony/error-handler", - "version": "v5.3.0", + "version": "v5.4.8", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "0e6768b8c0dcef26df087df2bbbaa143867a59b2" + "reference": "c1fcde614dfe99d62a83b796a53b8bad358b266a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/0e6768b8c0dcef26df087df2bbbaa143867a59b2", - "reference": "0e6768b8c0dcef26df087df2bbbaa143867a59b2", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/c1fcde614dfe99d62a83b796a53b8bad358b266a", + "reference": "c1fcde614dfe99d62a83b796a53b8bad358b266a", "shasum": "" }, "require": { "php": ">=7.2.5", - "psr/log": "^1.0", - "symfony/polyfill-php80": "^1.15", - "symfony/var-dumper": "^4.4|^5.0" + "psr/log": "^1|^2|^3", + "symfony/var-dumper": "^4.4|^5.0|^6.0" }, "require-dev": { - "symfony/deprecation-contracts": "^2.1", - "symfony/http-kernel": "^4.4|^5.0", - "symfony/serializer": "^4.4|^5.0" + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/http-kernel": "^4.4|^5.0|^6.0", + "symfony/serializer": "^4.4|^5.0|^6.0" }, + "bin": [ + "Resources/bin/patch-type-declarations" + ], "type": "library", "autoload": { "psr-4": { @@ -4314,7 +4949,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v5.3.0" + "source": "https://github.com/symfony/error-handler/tree/v5.4.8" }, "funding": [ { @@ -4330,27 +4965,27 @@ "type": "tidelift" } ], - "time": "2021-05-26T17:43:10+00:00" + "time": "2022-04-12T15:48:08+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v5.3.0", + "version": "v5.4.3", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "67a5f354afa8e2f231081b3fa11a5912f933c3ce" + "reference": "dec8a9f58d20df252b9cd89f1c6c1530f747685d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/67a5f354afa8e2f231081b3fa11a5912f933c3ce", - "reference": "67a5f354afa8e2f231081b3fa11a5912f933c3ce", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/dec8a9f58d20df252b9cd89f1c6c1530f747685d", + "reference": "dec8a9f58d20df252b9cd89f1c6c1530f747685d", "shasum": "" }, "require": { "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1", - "symfony/event-dispatcher-contracts": "^2", - "symfony/polyfill-php80": "^1.15" + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/event-dispatcher-contracts": "^2|^3", + "symfony/polyfill-php80": "^1.16" }, "conflict": { "symfony/dependency-injection": "<4.4" @@ -4360,14 +4995,14 @@ "symfony/event-dispatcher-implementation": "2.0" }, "require-dev": { - "psr/log": "~1.0", - "symfony/config": "^4.4|^5.0", - "symfony/dependency-injection": "^4.4|^5.0", - "symfony/error-handler": "^4.4|^5.0", - "symfony/expression-language": "^4.4|^5.0", - "symfony/http-foundation": "^4.4|^5.0", - "symfony/service-contracts": "^1.1|^2", - "symfony/stopwatch": "^4.4|^5.0" + "psr/log": "^1|^2|^3", + "symfony/config": "^4.4|^5.0|^6.0", + "symfony/dependency-injection": "^4.4|^5.0|^6.0", + "symfony/error-handler": "^4.4|^5.0|^6.0", + "symfony/expression-language": "^4.4|^5.0|^6.0", + "symfony/http-foundation": "^4.4|^5.0|^6.0", + "symfony/service-contracts": "^1.1|^2|^3", + "symfony/stopwatch": "^4.4|^5.0|^6.0" }, "suggest": { "symfony/dependency-injection": "", @@ -4399,7 +5034,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v5.3.0" + "source": "https://github.com/symfony/event-dispatcher/tree/v5.4.3" }, "funding": [ { @@ -4415,24 +5050,24 @@ "type": "tidelift" } ], - "time": "2021-05-26T17:43:10+00:00" + "time": "2022-01-02T09:53:40+00:00" }, { "name": "symfony/event-dispatcher-contracts", - "version": "v2.4.0", + "version": "v3.0.1", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "69fee1ad2332a7cbab3aca13591953da9cdb7a11" + "reference": "7bc61cc2db649b4637d331240c5346dcc7708051" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/69fee1ad2332a7cbab3aca13591953da9cdb7a11", - "reference": "69fee1ad2332a7cbab3aca13591953da9cdb7a11", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/7bc61cc2db649b4637d331240c5346dcc7708051", + "reference": "7bc61cc2db649b4637d331240c5346dcc7708051", "shasum": "" }, "require": { - "php": ">=7.2.5", + "php": ">=8.0.2", "psr/event-dispatcher": "^1" }, "suggest": { @@ -4441,7 +5076,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "2.4-dev" + "dev-main": "3.0-dev" }, "thanks": { "name": "symfony/contracts", @@ -4478,7 +5113,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v2.4.0" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.0.1" }, "funding": [ { @@ -4494,24 +5129,26 @@ "type": "tidelift" } ], - "time": "2021-03-23T23:28:01+00:00" + "time": "2022-01-02T09:55:41+00:00" }, { "name": "symfony/finder", - "version": "v5.3.0", + "version": "v5.4.8", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "0ae3f047bed4edff6fd35b26a9a6bfdc92c953c6" + "reference": "9b630f3427f3ebe7cd346c277a1408b00249dad9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/0ae3f047bed4edff6fd35b26a9a6bfdc92c953c6", - "reference": "0ae3f047bed4edff6fd35b26a9a6bfdc92c953c6", + "url": "https://api.github.com/repos/symfony/finder/zipball/9b630f3427f3ebe7cd346c277a1408b00249dad9", + "reference": "9b630f3427f3ebe7cd346c277a1408b00249dad9", "shasum": "" }, "require": { - "php": ">=7.2.5" + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-php80": "^1.16" }, "type": "library", "autoload": { @@ -4539,85 +5176,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v5.3.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-05-26T12:52:38+00:00" - }, - { - "name": "symfony/http-client-contracts", - "version": "v2.4.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/http-client-contracts.git", - "reference": "7e82f6084d7cae521a75ef2cb5c9457bbda785f4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/7e82f6084d7cae521a75ef2cb5c9457bbda785f4", - "reference": "7e82f6084d7cae521a75ef2cb5c9457bbda785f4", - "shasum": "" - }, - "require": { - "php": ">=7.2.5" - }, - "suggest": { - "symfony/http-client-implementation": "" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "2.4-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Contracts\\HttpClient\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Generic abstractions related to HTTP clients", - "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ], - "support": { - "source": "https://github.com/symfony/http-client-contracts/tree/v2.4.0" + "source": "https://github.com/symfony/finder/tree/v5.4.8" }, "funding": [ { @@ -4633,33 +5192,33 @@ "type": "tidelift" } ], - "time": "2021-04-11T23:07:08+00:00" + "time": "2022-04-15T08:07:45+00:00" }, { "name": "symfony/http-foundation", - "version": "v5.3.2", + "version": "v5.4.8", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "7b6dd714d95106b831aaa7f3c9c612ab886516bd" + "reference": "ff2818d1c3d49860bcae1f2cbb5eb00fcd3bf9e2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/7b6dd714d95106b831aaa7f3c9c612ab886516bd", - "reference": "7b6dd714d95106b831aaa7f3c9c612ab886516bd", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/ff2818d1c3d49860bcae1f2cbb5eb00fcd3bf9e2", + "reference": "ff2818d1c3d49860bcae1f2cbb5eb00fcd3bf9e2", "shasum": "" }, "require": { "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1", + "symfony/deprecation-contracts": "^2.1|^3", "symfony/polyfill-mbstring": "~1.1", - "symfony/polyfill-php80": "^1.15" + "symfony/polyfill-php80": "^1.16" }, "require-dev": { "predis/predis": "~1.0", - "symfony/cache": "^4.4|^5.0", - "symfony/expression-language": "^4.4|^5.0", - "symfony/mime": "^4.4|^5.0" + "symfony/cache": "^4.4|^5.0|^6.0", + "symfony/expression-language": "^4.4|^5.0|^6.0", + "symfony/mime": "^4.4|^5.0|^6.0" }, "suggest": { "symfony/mime": "To use the file extension guesser" @@ -4690,7 +5249,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v5.3.2" + "source": "https://github.com/symfony/http-foundation/tree/v5.4.8" }, "funding": [ { @@ -4706,36 +5265,35 @@ "type": "tidelift" } ], - "time": "2021-06-12T10:15:17+00:00" + "time": "2022-04-22T08:14:12+00:00" }, { "name": "symfony/http-kernel", - "version": "v5.3.2", + "version": "v5.4.8", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "e7021165d9dbfb4051296b8de827e92c8a7b5c87" + "reference": "cf7e61106abfc19b305ca0aedc41724ced89a02a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/e7021165d9dbfb4051296b8de827e92c8a7b5c87", - "reference": "e7021165d9dbfb4051296b8de827e92c8a7b5c87", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/cf7e61106abfc19b305ca0aedc41724ced89a02a", + "reference": "cf7e61106abfc19b305ca0aedc41724ced89a02a", "shasum": "" }, "require": { "php": ">=7.2.5", - "psr/log": "~1.0", - "symfony/deprecation-contracts": "^2.1", - "symfony/error-handler": "^4.4|^5.0", - "symfony/event-dispatcher": "^5.0", - "symfony/http-client-contracts": "^1.1|^2", - "symfony/http-foundation": "^5.3", + "psr/log": "^1|^2", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/error-handler": "^4.4|^5.0|^6.0", + "symfony/event-dispatcher": "^5.0|^6.0", + "symfony/http-foundation": "^5.3.7|^6.0", "symfony/polyfill-ctype": "^1.8", "symfony/polyfill-php73": "^1.9", - "symfony/polyfill-php80": "^1.15" + "symfony/polyfill-php80": "^1.16" }, "conflict": { - "symfony/browser-kit": "<4.4", + "symfony/browser-kit": "<5.4", "symfony/cache": "<5.0", "symfony/config": "<5.0", "symfony/console": "<4.4", @@ -4751,23 +5309,24 @@ "twig/twig": "<2.13" }, "provide": { - "psr/log-implementation": "1.0" + "psr/log-implementation": "1.0|2.0" }, "require-dev": { "psr/cache": "^1.0|^2.0|^3.0", - "symfony/browser-kit": "^4.4|^5.0", - "symfony/config": "^5.0", - "symfony/console": "^4.4|^5.0", - "symfony/css-selector": "^4.4|^5.0", - "symfony/dependency-injection": "^5.3", - "symfony/dom-crawler": "^4.4|^5.0", - "symfony/expression-language": "^4.4|^5.0", - "symfony/finder": "^4.4|^5.0", - "symfony/process": "^4.4|^5.0", - "symfony/routing": "^4.4|^5.0", - "symfony/stopwatch": "^4.4|^5.0", - "symfony/translation": "^4.4|^5.0", - "symfony/translation-contracts": "^1.1|^2", + "symfony/browser-kit": "^5.4|^6.0", + "symfony/config": "^5.0|^6.0", + "symfony/console": "^4.4|^5.0|^6.0", + "symfony/css-selector": "^4.4|^5.0|^6.0", + "symfony/dependency-injection": "^5.3|^6.0", + "symfony/dom-crawler": "^4.4|^5.0|^6.0", + "symfony/expression-language": "^4.4|^5.0|^6.0", + "symfony/finder": "^4.4|^5.0|^6.0", + "symfony/http-client-contracts": "^1.1|^2|^3", + "symfony/process": "^4.4|^5.0|^6.0", + "symfony/routing": "^4.4|^5.0|^6.0", + "symfony/stopwatch": "^4.4|^5.0|^6.0", + "symfony/translation": "^4.4|^5.0|^6.0", + "symfony/translation-contracts": "^1.1|^2|^3", "twig/twig": "^2.13|^3.0.4" }, "suggest": { @@ -4802,7 +5361,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v5.3.2" + "source": "https://github.com/symfony/http-kernel/tree/v5.4.8" }, "funding": [ { @@ -4818,28 +5377,28 @@ "type": "tidelift" } ], - "time": "2021-06-17T14:18:27+00:00" + "time": "2022-04-27T17:22:21+00:00" }, { "name": "symfony/mime", - "version": "v5.3.2", + "version": "v5.4.8", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "47dd7912152b82d0d4c8d9040dbc93d6232d472a" + "reference": "af49bc163ec3272f677bde3bc44c0d766c1fd662" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/47dd7912152b82d0d4c8d9040dbc93d6232d472a", - "reference": "47dd7912152b82d0d4c8d9040dbc93d6232d472a", + "url": "https://api.github.com/repos/symfony/mime/zipball/af49bc163ec3272f677bde3bc44c0d766c1fd662", + "reference": "af49bc163ec3272f677bde3bc44c0d766c1fd662", "shasum": "" }, "require": { "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1", + "symfony/deprecation-contracts": "^2.1|^3", "symfony/polyfill-intl-idn": "^1.10", "symfony/polyfill-mbstring": "^1.0", - "symfony/polyfill-php80": "^1.15" + "symfony/polyfill-php80": "^1.16" }, "conflict": { "egulias/email-validator": "~3.0.0", @@ -4850,10 +5409,10 @@ "require-dev": { "egulias/email-validator": "^2.1.10|^3.1", "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", - "symfony/dependency-injection": "^4.4|^5.0", - "symfony/property-access": "^4.4|^5.1", - "symfony/property-info": "^4.4|^5.1", - "symfony/serializer": "^5.2" + "symfony/dependency-injection": "^4.4|^5.0|^6.0", + "symfony/property-access": "^4.4|^5.1|^6.0", + "symfony/property-info": "^4.4|^5.1|^6.0", + "symfony/serializer": "^5.2|^6.0" }, "type": "library", "autoload": { @@ -4885,7 +5444,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v5.3.2" + "source": "https://github.com/symfony/mime/tree/v5.4.8" }, "funding": [ { @@ -4901,25 +5460,28 @@ "type": "tidelift" } ], - "time": "2021-06-09T10:58:01+00:00" + "time": "2022-04-12T15:48:08+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.23.0", + "version": "v1.25.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "46cd95797e9df938fdd2b03693b5fca5e64b01ce" + "reference": "30885182c981ab175d4d034db0f6f469898070ab" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/46cd95797e9df938fdd2b03693b5fca5e64b01ce", - "reference": "46cd95797e9df938fdd2b03693b5fca5e64b01ce", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/30885182c981ab175d4d034db0f6f469898070ab", + "reference": "30885182c981ab175d4d034db0f6f469898070ab", "shasum": "" }, "require": { "php": ">=7.1" }, + "provide": { + "ext-ctype": "*" + }, "suggest": { "ext-ctype": "For best performance" }, @@ -4934,12 +5496,12 @@ } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Ctype\\": "" - }, "files": [ "bootstrap.php" - ] + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -4964,7 +5526,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.23.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.25.0" }, "funding": [ { @@ -4980,25 +5542,28 @@ "type": "tidelift" } ], - "time": "2021-02-19T12:13:01+00:00" + "time": "2021-10-20T20:35:02+00:00" }, { "name": "symfony/polyfill-iconv", - "version": "v1.23.0", + "version": "v1.25.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-iconv.git", - "reference": "63b5bb7db83e5673936d6e3b8b3e022ff6474933" + "reference": "f1aed619e28cb077fc83fac8c4c0383578356e40" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/63b5bb7db83e5673936d6e3b8b3e022ff6474933", - "reference": "63b5bb7db83e5673936d6e3b8b3e022ff6474933", + "url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/f1aed619e28cb077fc83fac8c4c0383578356e40", + "reference": "f1aed619e28cb077fc83fac8c4c0383578356e40", "shasum": "" }, "require": { "php": ">=7.1" }, + "provide": { + "ext-iconv": "*" + }, "suggest": { "ext-iconv": "For best performance" }, @@ -5013,12 +5578,12 @@ } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Iconv\\": "" - }, "files": [ "bootstrap.php" - ] + ], + "psr-4": { + "Symfony\\Polyfill\\Iconv\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -5044,7 +5609,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-iconv/tree/v1.23.0" + "source": "https://github.com/symfony/polyfill-iconv/tree/v1.25.0" }, "funding": [ { @@ -5060,20 +5625,20 @@ "type": "tidelift" } ], - "time": "2021-05-27T09:27:20+00:00" + "time": "2022-01-04T09:04:05+00:00" }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.23.0", + "version": "v1.25.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "24b72c6baa32c746a4d0840147c9715e42bb68ab" + "reference": "81b86b50cf841a64252b439e738e97f4a34e2783" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/24b72c6baa32c746a4d0840147c9715e42bb68ab", - "reference": "24b72c6baa32c746a4d0840147c9715e42bb68ab", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/81b86b50cf841a64252b439e738e97f4a34e2783", + "reference": "81b86b50cf841a64252b439e738e97f4a34e2783", "shasum": "" }, "require": { @@ -5093,12 +5658,12 @@ } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Intl\\Grapheme\\": "" - }, "files": [ "bootstrap.php" - ] + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -5125,7 +5690,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.23.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.25.0" }, "funding": [ { @@ -5141,20 +5706,20 @@ "type": "tidelift" } ], - "time": "2021-05-27T09:17:38+00:00" + "time": "2021-11-23T21:10:46+00:00" }, { "name": "symfony/polyfill-intl-idn", - "version": "v1.23.0", + "version": "v1.25.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-idn.git", - "reference": "65bd267525e82759e7d8c4e8ceea44f398838e65" + "reference": "749045c69efb97c70d25d7463abba812e91f3a44" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/65bd267525e82759e7d8c4e8ceea44f398838e65", - "reference": "65bd267525e82759e7d8c4e8ceea44f398838e65", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/749045c69efb97c70d25d7463abba812e91f3a44", + "reference": "749045c69efb97c70d25d7463abba812e91f3a44", "shasum": "" }, "require": { @@ -5176,12 +5741,12 @@ } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Intl\\Idn\\": "" - }, "files": [ "bootstrap.php" - ] + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Idn\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -5212,7 +5777,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.23.0" + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.25.0" }, "funding": [ { @@ -5228,11 +5793,11 @@ "type": "tidelift" } ], - "time": "2021-05-27T09:27:20+00:00" + "time": "2021-09-14T14:02:44+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.23.0", + "version": "v1.25.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", @@ -5261,12 +5826,12 @@ } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Intl\\Normalizer\\": "" - }, "files": [ "bootstrap.php" ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, "classmap": [ "Resources/stubs" ] @@ -5296,7 +5861,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.23.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.25.0" }, "funding": [ { @@ -5316,21 +5881,24 @@ }, { "name": "symfony/polyfill-mbstring", - "version": "v1.23.0", + "version": "v1.25.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "2df51500adbaebdc4c38dea4c89a2e131c45c8a1" + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/2df51500adbaebdc4c38dea4c89a2e131c45c8a1", - "reference": "2df51500adbaebdc4c38dea4c89a2e131c45c8a1", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", "shasum": "" }, "require": { "php": ">=7.1" }, + "provide": { + "ext-mbstring": "*" + }, "suggest": { "ext-mbstring": "For best performance" }, @@ -5345,12 +5913,12 @@ } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - }, "files": [ "bootstrap.php" - ] + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -5376,7 +5944,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.23.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" }, "funding": [ { @@ -5392,7 +5960,7 @@ "type": "tidelift" } ], - "time": "2021-05-27T09:27:20+00:00" + "time": "2021-11-30T18:21:41+00:00" }, { "name": "symfony/polyfill-php56", @@ -5464,7 +6032,7 @@ }, { "name": "symfony/polyfill-php72", - "version": "v1.23.0", + "version": "v1.25.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php72.git", @@ -5490,12 +6058,12 @@ } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Php72\\": "" - }, "files": [ "bootstrap.php" - ] + ], + "psr-4": { + "Symfony\\Polyfill\\Php72\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -5520,7 +6088,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php72/tree/v1.23.0" + "source": "https://github.com/symfony/polyfill-php72/tree/v1.25.0" }, "funding": [ { @@ -5540,16 +6108,16 @@ }, { "name": "symfony/polyfill-php73", - "version": "v1.23.0", + "version": "v1.25.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php73.git", - "reference": "fba8933c384d6476ab14fb7b8526e5287ca7e010" + "reference": "cc5db0e22b3cb4111010e48785a97f670b350ca5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/fba8933c384d6476ab14fb7b8526e5287ca7e010", - "reference": "fba8933c384d6476ab14fb7b8526e5287ca7e010", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/cc5db0e22b3cb4111010e48785a97f670b350ca5", + "reference": "cc5db0e22b3cb4111010e48785a97f670b350ca5", "shasum": "" }, "require": { @@ -5566,12 +6134,12 @@ } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Php73\\": "" - }, "files": [ "bootstrap.php" ], + "psr-4": { + "Symfony\\Polyfill\\Php73\\": "" + }, "classmap": [ "Resources/stubs" ] @@ -5599,7 +6167,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php73/tree/v1.23.0" + "source": "https://github.com/symfony/polyfill-php73/tree/v1.25.0" }, "funding": [ { @@ -5615,20 +6183,20 @@ "type": "tidelift" } ], - "time": "2021-02-19T12:13:01+00:00" + "time": "2021-06-05T21:20:04+00:00" }, { "name": "symfony/polyfill-php80", - "version": "v1.23.0", + "version": "v1.25.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "eca0bf41ed421bed1b57c4958bab16aa86b757d0" + "reference": "4407588e0d3f1f52efb65fbe92babe41f37fe50c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/eca0bf41ed421bed1b57c4958bab16aa86b757d0", - "reference": "eca0bf41ed421bed1b57c4958bab16aa86b757d0", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/4407588e0d3f1f52efb65fbe92babe41f37fe50c", + "reference": "4407588e0d3f1f52efb65fbe92babe41f37fe50c", "shasum": "" }, "require": { @@ -5645,12 +6213,12 @@ } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Php80\\": "" - }, "files": [ "bootstrap.php" ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, "classmap": [ "Resources/stubs" ] @@ -5682,7 +6250,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.23.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.25.0" }, "funding": [ { @@ -5698,25 +6266,104 @@ "type": "tidelift" } ], - "time": "2021-02-19T12:13:01+00:00" + "time": "2022-03-04T08:16:47+00:00" + }, + { + "name": "symfony/polyfill-php81", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php81.git", + "reference": "5de4ba2d41b15f9bd0e19b2ab9674135813ec98f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/5de4ba2d41b15f9bd0e19b2ab9674135813ec98f", + "reference": "5de4ba2d41b15f9bd0e19b2ab9674135813ec98f", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php81\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php81/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-09-13T13:58:11+00:00" }, { "name": "symfony/process", - "version": "v5.3.2", + "version": "v5.4.8", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "714b47f9196de61a196d86c4bad5f09201b307df" + "reference": "597f3fff8e3e91836bb0bd38f5718b56ddbde2f3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/714b47f9196de61a196d86c4bad5f09201b307df", - "reference": "714b47f9196de61a196d86c4bad5f09201b307df", + "url": "https://api.github.com/repos/symfony/process/zipball/597f3fff8e3e91836bb0bd38f5718b56ddbde2f3", + "reference": "597f3fff8e3e91836bb0bd38f5718b56ddbde2f3", "shasum": "" }, "require": { "php": ">=7.2.5", - "symfony/polyfill-php80": "^1.15" + "symfony/polyfill-php80": "^1.16" }, "type": "library", "autoload": { @@ -5744,7 +6391,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v5.3.2" + "source": "https://github.com/symfony/process/tree/v5.4.8" }, "funding": [ { @@ -5760,26 +6407,26 @@ "type": "tidelift" } ], - "time": "2021-06-12T10:15:01+00:00" + "time": "2022-04-08T05:07:18+00:00" }, { "name": "symfony/routing", - "version": "v5.3.0", + "version": "v5.4.8", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "368e81376a8e049c37cb80ae87dbfbf411279199" + "reference": "e07817bb6244ea33ef5ad31abc4a9288bef3f2f7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/368e81376a8e049c37cb80ae87dbfbf411279199", - "reference": "368e81376a8e049c37cb80ae87dbfbf411279199", + "url": "https://api.github.com/repos/symfony/routing/zipball/e07817bb6244ea33ef5ad31abc4a9288bef3f2f7", + "reference": "e07817bb6244ea33ef5ad31abc4a9288bef3f2f7", "shasum": "" }, "require": { "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1", - "symfony/polyfill-php80": "^1.15" + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-php80": "^1.16" }, "conflict": { "doctrine/annotations": "<1.12", @@ -5789,12 +6436,12 @@ }, "require-dev": { "doctrine/annotations": "^1.12", - "psr/log": "~1.0", - "symfony/config": "^5.3", - "symfony/dependency-injection": "^4.4|^5.0", - "symfony/expression-language": "^4.4|^5.0", - "symfony/http-foundation": "^4.4|^5.0", - "symfony/yaml": "^4.4|^5.0" + "psr/log": "^1|^2|^3", + "symfony/config": "^5.3|^6.0", + "symfony/dependency-injection": "^4.4|^5.0|^6.0", + "symfony/expression-language": "^4.4|^5.0|^6.0", + "symfony/http-foundation": "^4.4|^5.0|^6.0", + "symfony/yaml": "^4.4|^5.0|^6.0" }, "suggest": { "symfony/config": "For using the all-in-one router or any loader", @@ -5834,7 +6481,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v5.3.0" + "source": "https://github.com/symfony/routing/tree/v5.4.8" }, "funding": [ { @@ -5850,25 +6497,29 @@ "type": "tidelift" } ], - "time": "2021-05-26T17:43:10+00:00" + "time": "2022-04-18T21:45:37+00:00" }, { "name": "symfony/service-contracts", - "version": "v2.4.0", + "version": "v2.5.1", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "f040a30e04b57fbcc9c6cbcf4dbaa96bd318b9bb" + "reference": "24d9dc654b83e91aa59f9d167b131bc3b5bea24c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f040a30e04b57fbcc9c6cbcf4dbaa96bd318b9bb", - "reference": "f040a30e04b57fbcc9c6cbcf4dbaa96bd318b9bb", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/24d9dc654b83e91aa59f9d167b131bc3b5bea24c", + "reference": "24d9dc654b83e91aa59f9d167b131bc3b5bea24c", "shasum": "" }, "require": { "php": ">=7.2.5", - "psr/container": "^1.1" + "psr/container": "^1.1", + "symfony/deprecation-contracts": "^2.1|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" }, "suggest": { "symfony/service-implementation": "" @@ -5876,7 +6527,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "2.4-dev" + "dev-main": "2.5-dev" }, "thanks": { "name": "symfony/contracts", @@ -5913,7 +6564,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v2.4.0" + "source": "https://github.com/symfony/service-contracts/tree/v2.5.1" }, "funding": [ { @@ -5929,44 +6580,46 @@ "type": "tidelift" } ], - "time": "2021-04-01T10:43:52+00:00" + "time": "2022-03-13T20:07:29+00:00" }, { "name": "symfony/string", - "version": "v5.3.2", + "version": "v6.0.8", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "0732e97e41c0a590f77e231afc16a327375d50b0" + "reference": "ac0aa5c2282e0de624c175b68d13f2c8f2e2649d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/0732e97e41c0a590f77e231afc16a327375d50b0", - "reference": "0732e97e41c0a590f77e231afc16a327375d50b0", + "url": "https://api.github.com/repos/symfony/string/zipball/ac0aa5c2282e0de624c175b68d13f2c8f2e2649d", + "reference": "ac0aa5c2282e0de624c175b68d13f2c8f2e2649d", "shasum": "" }, "require": { - "php": ">=7.2.5", + "php": ">=8.0.2", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-intl-grapheme": "~1.0", "symfony/polyfill-intl-normalizer": "~1.0", - "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php80": "~1.15" + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/translation-contracts": "<2.0" }, "require-dev": { - "symfony/error-handler": "^4.4|^5.0", - "symfony/http-client": "^4.4|^5.0", - "symfony/translation-contracts": "^1.1|^2", - "symfony/var-exporter": "^4.4|^5.0" + "symfony/error-handler": "^5.4|^6.0", + "symfony/http-client": "^5.4|^6.0", + "symfony/translation-contracts": "^2.0|^3.0", + "symfony/var-exporter": "^5.4|^6.0" }, "type": "library", "autoload": { - "psr-4": { - "Symfony\\Component\\String\\": "" - }, "files": [ "Resources/functions.php" ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, "exclude-from-classmap": [ "/Tests/" ] @@ -5996,7 +6649,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v5.3.2" + "source": "https://github.com/symfony/string/tree/v6.0.8" }, "funding": [ { @@ -6012,31 +6665,32 @@ "type": "tidelift" } ], - "time": "2021-06-06T09:51:56+00:00" + "time": "2022-04-22T08:18:02+00:00" }, { "name": "symfony/translation", - "version": "v5.3.2", + "version": "v5.4.8", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "7e2603bcc598e14804c4d2359d8dc4ee3c40391b" + "reference": "f5c0f6d1f20993b2606f3a5f36b1dc8c1899170b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/7e2603bcc598e14804c4d2359d8dc4ee3c40391b", - "reference": "7e2603bcc598e14804c4d2359d8dc4ee3c40391b", + "url": "https://api.github.com/repos/symfony/translation/zipball/f5c0f6d1f20993b2606f3a5f36b1dc8c1899170b", + "reference": "f5c0f6d1f20993b2606f3a5f36b1dc8c1899170b", "shasum": "" }, "require": { "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1", + "symfony/deprecation-contracts": "^2.1|^3", "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php80": "^1.15", + "symfony/polyfill-php80": "^1.16", "symfony/translation-contracts": "^2.3" }, "conflict": { "symfony/config": "<4.4", + "symfony/console": "<5.3", "symfony/dependency-injection": "<5.0", "symfony/http-kernel": "<5.0", "symfony/twig-bundle": "<5.0", @@ -6046,16 +6700,17 @@ "symfony/translation-implementation": "2.3" }, "require-dev": { - "psr/log": "~1.0", - "symfony/config": "^4.4|^5.0", - "symfony/console": "^4.4|^5.0", - "symfony/dependency-injection": "^5.0", - "symfony/finder": "^4.4|^5.0", - "symfony/http-kernel": "^5.0", - "symfony/intl": "^4.4|^5.0", + "psr/log": "^1|^2|^3", + "symfony/config": "^4.4|^5.0|^6.0", + "symfony/console": "^5.4|^6.0", + "symfony/dependency-injection": "^5.0|^6.0", + "symfony/finder": "^4.4|^5.0|^6.0", + "symfony/http-client-contracts": "^1.1|^2.0|^3.0", + "symfony/http-kernel": "^5.0|^6.0", + "symfony/intl": "^4.4|^5.0|^6.0", "symfony/polyfill-intl-icu": "^1.21", - "symfony/service-contracts": "^1.1.2|^2", - "symfony/yaml": "^4.4|^5.0" + "symfony/service-contracts": "^1.1.2|^2|^3", + "symfony/yaml": "^4.4|^5.0|^6.0" }, "suggest": { "psr/log-implementation": "To use logging capability in translator", @@ -6091,7 +6746,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v5.3.2" + "source": "https://github.com/symfony/translation/tree/v5.4.8" }, "funding": [ { @@ -6107,20 +6762,20 @@ "type": "tidelift" } ], - "time": "2021-06-06T09:51:56+00:00" + "time": "2022-04-22T08:14:12+00:00" }, { "name": "symfony/translation-contracts", - "version": "v2.4.0", + "version": "v2.5.1", "source": { "type": "git", "url": "https://github.com/symfony/translation-contracts.git", - "reference": "95c812666f3e91db75385749fe219c5e494c7f95" + "reference": "1211df0afa701e45a04253110e959d4af4ef0f07" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/95c812666f3e91db75385749fe219c5e494c7f95", - "reference": "95c812666f3e91db75385749fe219c5e494c7f95", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/1211df0afa701e45a04253110e959d4af4ef0f07", + "reference": "1211df0afa701e45a04253110e959d4af4ef0f07", "shasum": "" }, "require": { @@ -6132,7 +6787,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "2.4-dev" + "dev-main": "2.5-dev" }, "thanks": { "name": "symfony/contracts", @@ -6169,7 +6824,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/translation-contracts/tree/v2.4.0" + "source": "https://github.com/symfony/translation-contracts/tree/v2.5.1" }, "funding": [ { @@ -6185,26 +6840,26 @@ "type": "tidelift" } ], - "time": "2021-03-23T23:28:01+00:00" + "time": "2022-01-02T09:53:40+00:00" }, { "name": "symfony/var-dumper", - "version": "v5.3.2", + "version": "v5.4.8", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "905a22c68b292ffb6f20d7636c36b220d1fba5ae" + "reference": "cdcadd343d31ad16fc5e006b0de81ea307435053" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/905a22c68b292ffb6f20d7636c36b220d1fba5ae", - "reference": "905a22c68b292ffb6f20d7636c36b220d1fba5ae", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/cdcadd343d31ad16fc5e006b0de81ea307435053", + "reference": "cdcadd343d31ad16fc5e006b0de81ea307435053", "shasum": "" }, "require": { "php": ">=7.2.5", "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php80": "^1.15" + "symfony/polyfill-php80": "^1.16" }, "conflict": { "phpunit/phpunit": "<5.4.3", @@ -6212,8 +6867,9 @@ }, "require-dev": { "ext-iconv": "*", - "symfony/console": "^4.4|^5.0", - "symfony/process": "^4.4|^5.0", + "symfony/console": "^4.4|^5.0|^6.0", + "symfony/process": "^4.4|^5.0|^6.0", + "symfony/uid": "^5.1|^6.0", "twig/twig": "^2.13|^3.0.4" }, "suggest": { @@ -6257,7 +6913,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v5.3.2" + "source": "https://github.com/symfony/var-dumper/tree/v5.4.8" }, "funding": [ { @@ -6273,20 +6929,20 @@ "type": "tidelift" } ], - "time": "2021-06-06T09:51:56+00:00" + "time": "2022-04-26T13:19:20+00:00" }, { "name": "symfony/yaml", - "version": "v4.4.25", + "version": "v4.4.37", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "81cdac5536925c1c4b7b50aabc9ff6330b9eb5fc" + "reference": "d7f637cc0f0cc14beb0984f2bb50da560b271311" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/81cdac5536925c1c4b7b50aabc9ff6330b9eb5fc", - "reference": "81cdac5536925c1c4b7b50aabc9ff6330b9eb5fc", + "url": "https://api.github.com/repos/symfony/yaml/zipball/d7f637cc0f0cc14beb0984f2bb50da560b271311", + "reference": "d7f637cc0f0cc14beb0984f2bb50da560b271311", "shasum": "" }, "require": { @@ -6328,7 +6984,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v4.4.25" + "source": "https://github.com/symfony/yaml/tree/v4.4.37" }, "funding": [ { @@ -6344,30 +7000,30 @@ "type": "tidelift" } ], - "time": "2021-05-26T17:39:37+00:00" + "time": "2022-01-24T20:11:01+00:00" }, { "name": "tijsverkoyen/css-to-inline-styles", - "version": "2.2.3", + "version": "2.2.4", "source": { "type": "git", "url": "https://github.com/tijsverkoyen/CssToInlineStyles.git", - "reference": "b43b05cf43c1b6d849478965062b6ef73e223bb5" + "reference": "da444caae6aca7a19c0c140f68c6182e337d5b1c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/tijsverkoyen/CssToInlineStyles/zipball/b43b05cf43c1b6d849478965062b6ef73e223bb5", - "reference": "b43b05cf43c1b6d849478965062b6ef73e223bb5", + "url": "https://api.github.com/repos/tijsverkoyen/CssToInlineStyles/zipball/da444caae6aca7a19c0c140f68c6182e337d5b1c", + "reference": "da444caae6aca7a19c0c140f68c6182e337d5b1c", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "php": "^5.5 || ^7.0 || ^8.0", - "symfony/css-selector": "^2.7 || ^3.0 || ^4.0 || ^5.0" + "symfony/css-selector": "^2.7 || ^3.0 || ^4.0 || ^5.0 || ^6.0" }, "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0 || ^7.5" + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0 || ^7.5 || ^8.5.21 || ^9.5.10" }, "type": "library", "extra": { @@ -6395,37 +7051,37 @@ "homepage": "https://github.com/tijsverkoyen/CssToInlineStyles", "support": { "issues": "https://github.com/tijsverkoyen/CssToInlineStyles/issues", - "source": "https://github.com/tijsverkoyen/CssToInlineStyles/tree/2.2.3" + "source": "https://github.com/tijsverkoyen/CssToInlineStyles/tree/2.2.4" }, - "time": "2020-07-13T06:12:54+00:00" + "time": "2021-12-08T09:12:39+00:00" }, { "name": "vlucas/phpdotenv", - "version": "v5.3.0", + "version": "v5.4.1", "source": { "type": "git", "url": "https://github.com/vlucas/phpdotenv.git", - "reference": "b3eac5c7ac896e52deab4a99068e3f4ab12d9e56" + "reference": "264dce589e7ce37a7ba99cb901eed8249fbec92f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/b3eac5c7ac896e52deab4a99068e3f4ab12d9e56", - "reference": "b3eac5c7ac896e52deab4a99068e3f4ab12d9e56", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/264dce589e7ce37a7ba99cb901eed8249fbec92f", + "reference": "264dce589e7ce37a7ba99cb901eed8249fbec92f", "shasum": "" }, "require": { "ext-pcre": "*", - "graham-campbell/result-type": "^1.0.1", + "graham-campbell/result-type": "^1.0.2", "php": "^7.1.3 || ^8.0", - "phpoption/phpoption": "^1.7.4", - "symfony/polyfill-ctype": "^1.17", - "symfony/polyfill-mbstring": "^1.17", - "symfony/polyfill-php80": "^1.17" + "phpoption/phpoption": "^1.8", + "symfony/polyfill-ctype": "^1.23", + "symfony/polyfill-mbstring": "^1.23.1", + "symfony/polyfill-php80": "^1.23.1" }, "require-dev": { "bamarni/composer-bin-plugin": "^1.4.1", "ext-filter": "*", - "phpunit/phpunit": "^7.5.20 || ^8.5.14 || ^9.5.1" + "phpunit/phpunit": "^7.5.20 || ^8.5.21 || ^9.5.10" }, "suggest": { "ext-filter": "Required to use the boolean validator." @@ -6433,7 +7089,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "5.3-dev" + "dev-master": "5.4-dev" } }, "autoload": { @@ -6448,13 +7104,13 @@ "authors": [ { "name": "Graham Campbell", - "email": "graham@alt-three.com", - "homepage": "https://gjcampbell.co.uk/" + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" }, { "name": "Vance Lucas", "email": "vance@vancelucas.com", - "homepage": "https://vancelucas.com/" + "homepage": "https://github.com/vlucas" } ], "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.", @@ -6465,7 +7121,7 @@ ], "support": { "issues": "https://github.com/vlucas/phpdotenv/issues", - "source": "https://github.com/vlucas/phpdotenv/tree/v5.3.0" + "source": "https://github.com/vlucas/phpdotenv/tree/v5.4.1" }, "funding": [ { @@ -6477,20 +7133,20 @@ "type": "tidelift" } ], - "time": "2021-01-20T15:23:13+00:00" + "time": "2021-12-12T23:22:04+00:00" }, { "name": "voku/portable-ascii", - "version": "1.5.6", + "version": "1.6.1", "source": { "type": "git", "url": "https://github.com/voku/portable-ascii.git", - "reference": "80953678b19901e5165c56752d087fc11526017c" + "reference": "87337c91b9dfacee02452244ee14ab3c43bc485a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/voku/portable-ascii/zipball/80953678b19901e5165c56752d087fc11526017c", - "reference": "80953678b19901e5165c56752d087fc11526017c", + "url": "https://api.github.com/repos/voku/portable-ascii/zipball/87337c91b9dfacee02452244ee14ab3c43bc485a", + "reference": "87337c91b9dfacee02452244ee14ab3c43bc485a", "shasum": "" }, "require": { @@ -6527,7 +7183,7 @@ ], "support": { "issues": "https://github.com/voku/portable-ascii/issues", - "source": "https://github.com/voku/portable-ascii/tree/1.5.6" + "source": "https://github.com/voku/portable-ascii/tree/1.6.1" }, "funding": [ { @@ -6551,7 +7207,7 @@ "type": "tidelift" } ], - "time": "2020-11-12T00:07:28+00:00" + "time": "2022-01-24T18:55:24+00:00" }, { "name": "webmozart/assert", @@ -6615,54 +7271,54 @@ "packages-dev": [ { "name": "barryvdh/laravel-debugbar", - "version": "v3.6.2", + "version": "v3.6.7", "source": { "type": "git", "url": "https://github.com/barryvdh/laravel-debugbar.git", - "reference": "70b89754913fd89fef16d0170a91dbc2a5cd633a" + "reference": "b96f9820aaf1ff9afe945207883149e1c7afb298" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/70b89754913fd89fef16d0170a91dbc2a5cd633a", - "reference": "70b89754913fd89fef16d0170a91dbc2a5cd633a", + "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/b96f9820aaf1ff9afe945207883149e1c7afb298", + "reference": "b96f9820aaf1ff9afe945207883149e1c7afb298", "shasum": "" }, "require": { - "illuminate/routing": "^6|^7|^8", - "illuminate/session": "^6|^7|^8", - "illuminate/support": "^6|^7|^8", - "maximebf/debugbar": "^1.16.3", + "illuminate/routing": "^6|^7|^8|^9", + "illuminate/session": "^6|^7|^8|^9", + "illuminate/support": "^6|^7|^8|^9", + "maximebf/debugbar": "^1.17.2", "php": ">=7.2", - "symfony/debug": "^4.3|^5", - "symfony/finder": "^4.3|^5" + "symfony/debug": "^4.3|^5|^6", + "symfony/finder": "^4.3|^5|^6" }, "require-dev": { "mockery/mockery": "^1.3.3", - "orchestra/testbench-dusk": "^4|^5|^6", + "orchestra/testbench-dusk": "^4|^5|^6|^7", "phpunit/phpunit": "^8.5|^9.0", "squizlabs/php_codesniffer": "^3.5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.5-dev" + "dev-master": "3.6-dev" }, "laravel": { "providers": [ "Barryvdh\\Debugbar\\ServiceProvider" ], "aliases": { - "Debugbar": "Barryvdh\\Debugbar\\Facade" + "Debugbar": "Barryvdh\\Debugbar\\Facades\\Debugbar" } } }, "autoload": { - "psr-4": { - "Barryvdh\\Debugbar\\": "src/" - }, "files": [ "src/helpers.php" - ] + ], + "psr-4": { + "Barryvdh\\Debugbar\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -6684,7 +7340,7 @@ ], "support": { "issues": "https://github.com/barryvdh/laravel-debugbar/issues", - "source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.6.2" + "source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.6.7" }, "funding": [ { @@ -6696,30 +7352,30 @@ "type": "github" } ], - "time": "2021-06-14T14:29:26+00:00" + "time": "2022-02-09T07:52:32+00:00" }, { "name": "barryvdh/laravel-ide-helper", - "version": "v2.10.0", + "version": "v2.12.3", "source": { "type": "git", "url": "https://github.com/barryvdh/laravel-ide-helper.git", - "reference": "73b1012b927633a1b4cd623c2e6b1678e6faef08" + "reference": "3ba1e2573b38f72107b8aacc4ee177fcab30a550" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/73b1012b927633a1b4cd623c2e6b1678e6faef08", - "reference": "73b1012b927633a1b4cd623c2e6b1678e6faef08", + "url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/3ba1e2573b38f72107b8aacc4ee177fcab30a550", + "reference": "3ba1e2573b38f72107b8aacc4ee177fcab30a550", "shasum": "" }, "require": { "barryvdh/reflection-docblock": "^2.0.6", - "composer/composer": "^1.6 || ^2", + "composer/pcre": "^1 || ^2 || ^3", "doctrine/dbal": "^2.6 || ^3", "ext-json": "*", - "illuminate/console": "^8", - "illuminate/filesystem": "^8", - "illuminate/support": "^8", + "illuminate/console": "^8 || ^9", + "illuminate/filesystem": "^8 || ^9", + "illuminate/support": "^8 || ^9", "nikic/php-parser": "^4.7", "php": "^7.3 || ^8.0", "phpdocumentor/type-resolver": "^1.1.0" @@ -6727,21 +7383,21 @@ "require-dev": { "ext-pdo_sqlite": "*", "friendsofphp/php-cs-fixer": "^2", - "illuminate/config": "^8", - "illuminate/view": "^8", + "illuminate/config": "^8 || ^9", + "illuminate/view": "^8 || ^9", "mockery/mockery": "^1.4", - "orchestra/testbench": "^6", + "orchestra/testbench": "^6 || ^7", "phpunit/phpunit": "^8.5 || ^9", "spatie/phpunit-snapshot-assertions": "^3 || ^4", "vimeo/psalm": "^3.12" }, "suggest": { - "illuminate/events": "Required for automatic helper generation (^6|^7|^8)." + "illuminate/events": "Required for automatic helper generation (^6|^7|^8|^9)." }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.9-dev" + "dev-master": "2.12-dev" }, "laravel": { "providers": [ @@ -6778,15 +7434,19 @@ ], "support": { "issues": "https://github.com/barryvdh/laravel-ide-helper/issues", - "source": "https://github.com/barryvdh/laravel-ide-helper/tree/v2.10.0" + "source": "https://github.com/barryvdh/laravel-ide-helper/tree/v2.12.3" }, "funding": [ + { + "url": "https://fruitcake.nl", + "type": "custom" + }, { "url": "https://github.com/barryvdh", "type": "github" } ], - "time": "2021-04-09T06:17:55+00:00" + "time": "2022-03-06T14:33:42+00:00" }, { "name": "barryvdh/reflection-docblock", @@ -6841,268 +7501,26 @@ "time": "2018-12-13T10:34:14+00:00" }, { - "name": "composer/ca-bundle", - "version": "1.2.10", - "source": { - "type": "git", - "url": "https://github.com/composer/ca-bundle.git", - "reference": "9fdb22c2e97a614657716178093cd1da90a64aa8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/9fdb22c2e97a614657716178093cd1da90a64aa8", - "reference": "9fdb22c2e97a614657716178093cd1da90a64aa8", - "shasum": "" - }, - "require": { - "ext-openssl": "*", - "ext-pcre": "*", - "php": "^5.3.2 || ^7.0 || ^8.0" - }, - "require-dev": { - "phpstan/phpstan": "^0.12.55", - "psr/log": "^1.0", - "symfony/phpunit-bridge": "^4.2 || ^5", - "symfony/process": "^2.5 || ^3.0 || ^4.0 || ^5.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Composer\\CaBundle\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" - } - ], - "description": "Lets you find a path to the system CA bundle, and includes a fallback to the Mozilla CA bundle.", - "keywords": [ - "cabundle", - "cacert", - "certificate", - "ssl", - "tls" - ], - "support": { - "irc": "irc://irc.freenode.org/composer", - "issues": "https://github.com/composer/ca-bundle/issues", - "source": "https://github.com/composer/ca-bundle/tree/1.2.10" - }, - "funding": [ - { - "url": "https://packagist.com", - "type": "custom" - }, - { - "url": "https://github.com/composer", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" - } - ], - "time": "2021-06-07T13:58:28+00:00" - }, - { - "name": "composer/composer", - "version": "2.1.3", - "source": { - "type": "git", - "url": "https://github.com/composer/composer.git", - "reference": "fc5c4573aafce3a018eb7f1f8f91cea423970f2e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/composer/composer/zipball/fc5c4573aafce3a018eb7f1f8f91cea423970f2e", - "reference": "fc5c4573aafce3a018eb7f1f8f91cea423970f2e", - "shasum": "" - }, - "require": { - "composer/ca-bundle": "^1.0", - "composer/metadata-minifier": "^1.0", - "composer/semver": "^3.0", - "composer/spdx-licenses": "^1.2", - "composer/xdebug-handler": "^2.0", - "justinrainbow/json-schema": "^5.2.10", - "php": "^5.3.2 || ^7.0 || ^8.0", - "psr/log": "^1.0", - "react/promise": "^1.2 || ^2.7", - "seld/jsonlint": "^1.4", - "seld/phar-utils": "^1.0", - "symfony/console": "^2.8.52 || ^3.4.35 || ^4.4 || ^5.0 || ^6.0", - "symfony/filesystem": "^2.8.52 || ^3.4.35 || ^4.4 || ^5.0 || ^6.0", - "symfony/finder": "^2.8.52 || ^3.4.35 || ^4.4 || ^5.0 || ^6.0", - "symfony/process": "^2.8.52 || ^3.4.35 || ^4.4 || ^5.0 || ^6.0" - }, - "require-dev": { - "phpspec/prophecy": "^1.10", - "symfony/phpunit-bridge": "^4.2 || ^5.0 || ^6.0" - }, - "suggest": { - "ext-openssl": "Enabling the openssl extension allows you to access https URLs for repositories and packages", - "ext-zip": "Enabling the zip extension allows you to unzip archives", - "ext-zlib": "Allow gzip compression of HTTP requests" - }, - "bin": [ - "bin/composer" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.1-dev" - } - }, - "autoload": { - "psr-4": { - "Composer\\": "src/Composer" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nils Adermann", - "email": "naderman@naderman.de", - "homepage": "https://www.naderman.de" - }, - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "https://seld.be" - } - ], - "description": "Composer helps you declare, manage and install dependencies of PHP projects. It ensures you have the right stack everywhere.", - "homepage": "https://getcomposer.org/", - "keywords": [ - "autoload", - "dependency", - "package" - ], - "support": { - "irc": "irc://irc.freenode.org/composer", - "issues": "https://github.com/composer/composer/issues", - "source": "https://github.com/composer/composer/tree/2.1.3" - }, - "funding": [ - { - "url": "https://packagist.com", - "type": "custom" - }, - { - "url": "https://github.com/composer", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" - } - ], - "time": "2021-06-09T14:31:20+00:00" - }, - { - "name": "composer/metadata-minifier", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/composer/metadata-minifier.git", - "reference": "c549d23829536f0d0e984aaabbf02af91f443207" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/composer/metadata-minifier/zipball/c549d23829536f0d0e984aaabbf02af91f443207", - "reference": "c549d23829536f0d0e984aaabbf02af91f443207", - "shasum": "" - }, - "require": { - "php": "^5.3.2 || ^7.0 || ^8.0" - }, - "require-dev": { - "composer/composer": "^2", - "phpstan/phpstan": "^0.12.55", - "symfony/phpunit-bridge": "^4.2 || ^5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Composer\\MetadataMinifier\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" - } - ], - "description": "Small utility library that handles metadata minification and expansion.", - "keywords": [ - "composer", - "compression" - ], - "support": { - "issues": "https://github.com/composer/metadata-minifier/issues", - "source": "https://github.com/composer/metadata-minifier/tree/1.0.0" - }, - "funding": [ - { - "url": "https://packagist.com", - "type": "custom" - }, - { - "url": "https://github.com/composer", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" - } - ], - "time": "2021-04-07T13:37:33+00:00" - }, - { - "name": "composer/semver", - "version": "3.2.5", + "name": "composer/pcre", + "version": "3.0.0", "source": { "type": "git", - "url": "https://github.com/composer/semver.git", - "reference": "31f3ea725711245195f62e54ffa402d8ef2fdba9" + "url": "https://github.com/composer/pcre.git", + "reference": "e300eb6c535192decd27a85bc72a9290f0d6b3bd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/31f3ea725711245195f62e54ffa402d8ef2fdba9", - "reference": "31f3ea725711245195f62e54ffa402d8ef2fdba9", + "url": "https://api.github.com/repos/composer/pcre/zipball/e300eb6c535192decd27a85bc72a9290f0d6b3bd", + "reference": "e300eb6c535192decd27a85bc72a9290f0d6b3bd", "shasum": "" }, "require": { - "php": "^5.3.2 || ^7.0 || ^8.0" + "php": "^7.4 || ^8.0" }, "require-dev": { - "phpstan/phpstan": "^0.12.54", - "symfony/phpunit-bridge": "^4.2 || ^5" + "phpstan/phpstan": "^1.3", + "phpstan/phpstan-strict-rules": "^1.1", + "symfony/phpunit-bridge": "^5" }, "type": "library", "extra": { @@ -7112,7 +7530,7 @@ }, "autoload": { "psr-4": { - "Composer\\Semver\\": "src" + "Composer\\Pcre\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -7120,33 +7538,22 @@ "MIT" ], "authors": [ - { - "name": "Nils Adermann", - "email": "naderman@naderman.de", - "homepage": "http://www.naderman.de" - }, { "name": "Jordi Boggiano", "email": "j.boggiano@seld.be", "homepage": "http://seld.be" - }, - { - "name": "Rob Bast", - "email": "rob.bast@gmail.com", - "homepage": "http://robbast.nl" } ], - "description": "Semver library that offers utilities, version constraint parsing and validation.", + "description": "PCRE wrapping library that offers type-safe preg_* replacements.", "keywords": [ - "semantic", - "semver", - "validation", - "versioning" + "PCRE", + "preg", + "regex", + "regular expression" ], "support": { - "irc": "irc://irc.freenode.org/composer", - "issues": "https://github.com/composer/semver/issues", - "source": "https://github.com/composer/semver/tree/3.2.5" + "issues": "https://github.com/composer/pcre/issues", + "source": "https://github.com/composer/pcre/tree/3.0.0" }, "funding": [ { @@ -7162,37 +7569,38 @@ "type": "tidelift" } ], - "time": "2021-05-24T12:41:47+00:00" + "time": "2022-02-25T20:21:48+00:00" }, { - "name": "composer/spdx-licenses", - "version": "1.5.5", + "name": "composer/semver", + "version": "3.3.2", "source": { "type": "git", - "url": "https://github.com/composer/spdx-licenses.git", - "reference": "de30328a7af8680efdc03e396aad24befd513200" + "url": "https://github.com/composer/semver.git", + "reference": "3953f23262f2bff1919fc82183ad9acb13ff62c9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/de30328a7af8680efdc03e396aad24befd513200", - "reference": "de30328a7af8680efdc03e396aad24befd513200", + "url": "https://api.github.com/repos/composer/semver/zipball/3953f23262f2bff1919fc82183ad9acb13ff62c9", + "reference": "3953f23262f2bff1919fc82183ad9acb13ff62c9", "shasum": "" }, "require": { "php": "^5.3.2 || ^7.0 || ^8.0" }, "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7 || 6.5 - 7" + "phpstan/phpstan": "^1.4", + "symfony/phpunit-bridge": "^4.2 || ^5" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "1.x-dev" + "dev-main": "3.x-dev" } }, "autoload": { "psr-4": { - "Composer\\Spdx\\": "src" + "Composer\\Semver\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -7216,16 +7624,17 @@ "homepage": "http://robbast.nl" } ], - "description": "SPDX licenses list and validation library.", + "description": "Semver library that offers utilities, version constraint parsing and validation.", "keywords": [ - "license", - "spdx", - "validator" + "semantic", + "semver", + "validation", + "versioning" ], "support": { "irc": "irc://irc.freenode.org/composer", - "issues": "https://github.com/composer/spdx-licenses/issues", - "source": "https://github.com/composer/spdx-licenses/tree/1.5.5" + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.3.2" }, "funding": [ { @@ -7241,25 +7650,25 @@ "type": "tidelift" } ], - "time": "2020-12-03T16:04:16+00:00" + "time": "2022-04-01T19:23:25+00:00" }, { "name": "composer/xdebug-handler", - "version": "2.0.1", + "version": "2.0.2", "source": { "type": "git", "url": "https://github.com/composer/xdebug-handler.git", - "reference": "964adcdd3a28bf9ed5d9ac6450064e0d71ed7496" + "reference": "84674dd3a7575ba617f5a76d7e9e29a7d3891339" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/964adcdd3a28bf9ed5d9ac6450064e0d71ed7496", - "reference": "964adcdd3a28bf9ed5d9ac6450064e0d71ed7496", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/84674dd3a7575ba617f5a76d7e9e29a7d3891339", + "reference": "84674dd3a7575ba617f5a76d7e9e29a7d3891339", "shasum": "" }, "require": { "php": "^5.3.2 || ^7.0 || ^8.0", - "psr/log": "^1.0" + "psr/log": "^1 || ^2 || ^3" }, "require-dev": { "phpstan/phpstan": "^0.12.55", @@ -7289,7 +7698,7 @@ "support": { "irc": "irc://irc.freenode.org/composer", "issues": "https://github.com/composer/xdebug-handler/issues", - "source": "https://github.com/composer/xdebug-handler/tree/2.0.1" + "source": "https://github.com/composer/xdebug-handler/tree/2.0.2" }, "funding": [ { @@ -7305,20 +7714,20 @@ "type": "tidelift" } ], - "time": "2021-05-05T19:37:51+00:00" + "time": "2021-07-31T17:03:58+00:00" }, { "name": "doctrine/annotations", - "version": "1.13.1", + "version": "1.13.2", "source": { "type": "git", "url": "https://github.com/doctrine/annotations.git", - "reference": "e6e7b7d5b45a2f2abc5460cc6396480b2b1d321f" + "reference": "5b668aef16090008790395c02c893b1ba13f7e08" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/annotations/zipball/e6e7b7d5b45a2f2abc5460cc6396480b2b1d321f", - "reference": "e6e7b7d5b45a2f2abc5460cc6396480b2b1d321f", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/5b668aef16090008790395c02c893b1ba13f7e08", + "reference": "5b668aef16090008790395c02c893b1ba13f7e08", "shasum": "" }, "require": { @@ -7375,35 +7784,36 @@ ], "support": { "issues": "https://github.com/doctrine/annotations/issues", - "source": "https://github.com/doctrine/annotations/tree/1.13.1" + "source": "https://github.com/doctrine/annotations/tree/1.13.2" }, - "time": "2021-05-16T18:07:53+00:00" + "time": "2021-08-05T19:00:23+00:00" }, { "name": "doctrine/instantiator", - "version": "1.4.0", + "version": "1.4.1", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b" + "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/d56bf6102915de5702778fe20f2de3b2fe570b5b", - "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/10dcfce151b967d20fde1b34ae6640712c3891bc", + "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc", "shasum": "" }, "require": { "php": "^7.1 || ^8.0" }, "require-dev": { - "doctrine/coding-standard": "^8.0", + "doctrine/coding-standard": "^9", "ext-pdo": "*", "ext-phar": "*", - "phpbench/phpbench": "^0.13 || 1.0.0-alpha2", - "phpstan/phpstan": "^0.12", - "phpstan/phpstan-phpunit": "^0.12", - "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" + "phpbench/phpbench": "^0.16 || ^1", + "phpstan/phpstan": "^1.4", + "phpstan/phpstan-phpunit": "^1", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "vimeo/psalm": "^4.22" }, "type": "library", "autoload": { @@ -7430,7 +7840,7 @@ ], "support": { "issues": "https://github.com/doctrine/instantiator/issues", - "source": "https://github.com/doctrine/instantiator/tree/1.4.0" + "source": "https://github.com/doctrine/instantiator/tree/1.4.1" }, "funding": [ { @@ -7446,20 +7856,20 @@ "type": "tidelift" } ], - "time": "2020-11-10T18:47:58+00:00" + "time": "2022-03-03T08:28:38+00:00" }, { "name": "facade/flare-client-php", - "version": "1.8.1", + "version": "1.9.1", "source": { "type": "git", "url": "https://github.com/facade/flare-client-php.git", - "reference": "47b639dc02bcfdfc4ebb83de703856fa01e35f5f" + "reference": "b2adf1512755637d0cef4f7d1b54301325ac78ed" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/facade/flare-client-php/zipball/47b639dc02bcfdfc4ebb83de703856fa01e35f5f", - "reference": "47b639dc02bcfdfc4ebb83de703856fa01e35f5f", + "url": "https://api.github.com/repos/facade/flare-client-php/zipball/b2adf1512755637d0cef4f7d1b54301325ac78ed", + "reference": "b2adf1512755637d0cef4f7d1b54301325ac78ed", "shasum": "" }, "require": { @@ -7482,12 +7892,12 @@ } }, "autoload": { - "psr-4": { - "Facade\\FlareClient\\": "src" - }, "files": [ "src/helpers.php" - ] + ], + "psr-4": { + "Facade\\FlareClient\\": "src" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -7503,7 +7913,7 @@ ], "support": { "issues": "https://github.com/facade/flare-client-php/issues", - "source": "https://github.com/facade/flare-client-php/tree/1.8.1" + "source": "https://github.com/facade/flare-client-php/tree/1.9.1" }, "funding": [ { @@ -7511,28 +7921,28 @@ "type": "github" } ], - "time": "2021-05-31T19:23:29+00:00" + "time": "2021-09-13T12:16:46+00:00" }, { "name": "facade/ignition", - "version": "2.10.2", + "version": "2.17.5", "source": { "type": "git", "url": "https://github.com/facade/ignition.git", - "reference": "43688227bbf27c43bc1ad83af224f135b6ef0ff4" + "reference": "1d71996f83c9a5a7807331b8986ac890352b7a0c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/facade/ignition/zipball/43688227bbf27c43bc1ad83af224f135b6ef0ff4", - "reference": "43688227bbf27c43bc1ad83af224f135b6ef0ff4", + "url": "https://api.github.com/repos/facade/ignition/zipball/1d71996f83c9a5a7807331b8986ac890352b7a0c", + "reference": "1d71996f83c9a5a7807331b8986ac890352b7a0c", "shasum": "" }, "require": { + "ext-curl": "*", "ext-json": "*", "ext-mbstring": "*", - "facade/flare-client-php": "^1.6", + "facade/flare-client-php": "^1.9.1", "facade/ignition-contracts": "^1.0.2", - "filp/whoops": "^2.4", "illuminate/support": "^7.0|^8.0", "monolog/monolog": "^2.0", "php": "^7.2.5|^8.0", @@ -7541,6 +7951,7 @@ }, "require-dev": { "friendsofphp/php-cs-fixer": "^2.14", + "livewire/livewire": "^2.4", "mockery/mockery": "^1.3", "orchestra/testbench": "^5.0|^6.0", "psalm/plugin-laravel": "^1.2" @@ -7563,12 +7974,12 @@ } }, "autoload": { - "psr-4": { - "Facade\\Ignition\\": "src" - }, "files": [ "src/helpers.php" - ] + ], + "psr-4": { + "Facade\\Ignition\\": "src" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -7588,7 +7999,7 @@ "issues": "https://github.com/facade/ignition/issues", "source": "https://github.com/facade/ignition" }, - "time": "2021-06-11T06:57:25+00:00" + "time": "2022-02-23T18:31:24+00:00" }, { "name": "facade/ignition-contracts", @@ -7645,32 +8056,34 @@ }, { "name": "fakerphp/faker", - "version": "v1.14.1", + "version": "v1.19.0", "source": { "type": "git", "url": "https://github.com/FakerPHP/Faker.git", - "reference": "ed22aee8d17c7b396f74a58b1e7fefa4f90d5ef1" + "reference": "d7f08a622b3346766325488aa32ddc93ccdecc75" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/ed22aee8d17c7b396f74a58b1e7fefa4f90d5ef1", - "reference": "ed22aee8d17c7b396f74a58b1e7fefa4f90d5ef1", + "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/d7f08a622b3346766325488aa32ddc93ccdecc75", + "reference": "d7f08a622b3346766325488aa32ddc93ccdecc75", "shasum": "" }, "require": { "php": "^7.1 || ^8.0", - "psr/container": "^1.0", - "symfony/deprecation-contracts": "^2.2" + "psr/container": "^1.0 || ^2.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" }, "conflict": { "fzaninotto/faker": "*" }, "require-dev": { "bamarni/composer-bin-plugin": "^1.4.1", + "doctrine/persistence": "^1.3 || ^2.0", "ext-intl": "*", "symfony/phpunit-bridge": "^4.4 || ^5.2" }, "suggest": { + "doctrine/orm": "Required to use Faker\\ORM\\Doctrine", "ext-curl": "Required by Faker\\Provider\\Image to download images.", "ext-dom": "Required by Faker\\Provider\\HtmlLorem for generating random HTML.", "ext-iconv": "Required by Faker\\Provider\\ru_RU\\Text::realText() for generating real Russian text.", @@ -7679,7 +8092,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "v1.15-dev" + "dev-main": "v1.19-dev" } }, "autoload": { @@ -7704,27 +8117,27 @@ ], "support": { "issues": "https://github.com/FakerPHP/Faker/issues", - "source": "https://github.com/FakerPHP/Faker/tree/v.1.14.1" + "source": "https://github.com/FakerPHP/Faker/tree/v1.19.0" }, - "time": "2021-03-30T06:27:33+00:00" + "time": "2022-02-02T17:38:57+00:00" }, { "name": "filp/whoops", - "version": "2.13.0", + "version": "2.14.5", "source": { "type": "git", "url": "https://github.com/filp/whoops.git", - "reference": "2edbc73a4687d9085c8f20f398eebade844e8424" + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/2edbc73a4687d9085c8f20f398eebade844e8424", - "reference": "2edbc73a4687d9085c8f20f398eebade844e8424", + "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", "shasum": "" }, "require": { "php": "^5.5.9 || ^7.0 || ^8.0", - "psr/log": "^1.0.1" + "psr/log": "^1.0.1 || ^2.0 || ^3.0" }, "require-dev": { "mockery/mockery": "^0.9 || ^1.0", @@ -7769,7 +8182,7 @@ ], "support": { "issues": "https://github.com/filp/whoops/issues", - "source": "https://github.com/filp/whoops/tree/2.13.0" + "source": "https://github.com/filp/whoops/tree/2.14.5" }, "funding": [ { @@ -7777,20 +8190,20 @@ "type": "github" } ], - "time": "2021-06-04T12:00:00+00:00" + "time": "2022-01-07T12:00:00+00:00" }, { "name": "friendsofphp/php-cs-fixer", - "version": "v2.19.0", + "version": "v2.19.3", "source": { "type": "git", "url": "https://github.com/FriendsOfPHP/PHP-CS-Fixer.git", - "reference": "d5b8a9d852b292c2f8a035200fa6844b1f82300b" + "reference": "75ac86f33fab4714ea5a39a396784d83ae3b5ed8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/d5b8a9d852b292c2f8a035200fa6844b1f82300b", - "reference": "d5b8a9d852b292c2f8a035200fa6844b1f82300b", + "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/75ac86f33fab4714ea5a39a396784d83ae3b5ed8", + "reference": "75ac86f33fab4714ea5a39a396784d83ae3b5ed8", "shasum": "" }, "require": { @@ -7878,7 +8291,7 @@ "description": "A tool to automatically fix PHP code style", "support": { "issues": "https://github.com/FriendsOfPHP/PHP-CS-Fixer/issues", - "source": "https://github.com/FriendsOfPHP/PHP-CS-Fixer/tree/v2.19.0" + "source": "https://github.com/FriendsOfPHP/PHP-CS-Fixer/tree/v2.19.3" }, "funding": [ { @@ -7886,7 +8299,7 @@ "type": "github" } ], - "time": "2021-05-03T21:43:24+00:00" + "time": "2021-11-15T17:17:55+00:00" }, { "name": "hamcrest/hamcrest-php", @@ -7939,106 +8352,36 @@ }, "time": "2020-07-09T08:09:16+00:00" }, - { - "name": "justinrainbow/json-schema", - "version": "5.2.10", - "source": { - "type": "git", - "url": "https://github.com/justinrainbow/json-schema.git", - "reference": "2ba9c8c862ecd5510ed16c6340aa9f6eadb4f31b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/2ba9c8c862ecd5510ed16c6340aa9f6eadb4f31b", - "reference": "2ba9c8c862ecd5510ed16c6340aa9f6eadb4f31b", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "~2.2.20||~2.15.1", - "json-schema/json-schema-test-suite": "1.2.0", - "phpunit/phpunit": "^4.8.35" - }, - "bin": [ - "bin/validate-json" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "JsonSchema\\": "src/JsonSchema/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Bruno Prieto Reis", - "email": "bruno.p.reis@gmail.com" - }, - { - "name": "Justin Rainbow", - "email": "justin.rainbow@gmail.com" - }, - { - "name": "Igor Wiedler", - "email": "igor@wiedler.ch" - }, - { - "name": "Robert Schönthal", - "email": "seroscho@googlemail.com" - } - ], - "description": "A library to validate a json schema.", - "homepage": "https://github.com/justinrainbow/json-schema", - "keywords": [ - "json", - "schema" - ], - "support": { - "issues": "https://github.com/justinrainbow/json-schema/issues", - "source": "https://github.com/justinrainbow/json-schema/tree/5.2.10" - }, - "time": "2020-05-27T16:41:55+00:00" - }, { "name": "laravel/dusk", - "version": "v6.15.0", + "version": "v6.23.1", "source": { "type": "git", "url": "https://github.com/laravel/dusk.git", - "reference": "45b55fa20321086c4f8cc4e712cbe54db644e21c" + "reference": "41f6deb42ae42b9b7dae1c32c03cb35d365d3118" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/dusk/zipball/45b55fa20321086c4f8cc4e712cbe54db644e21c", - "reference": "45b55fa20321086c4f8cc4e712cbe54db644e21c", + "url": "https://api.github.com/repos/laravel/dusk/zipball/41f6deb42ae42b9b7dae1c32c03cb35d365d3118", + "reference": "41f6deb42ae42b9b7dae1c32c03cb35d365d3118", "shasum": "" }, "require": { "ext-json": "*", "ext-zip": "*", - "illuminate/console": "^6.0|^7.0|^8.0", - "illuminate/support": "^6.0|^7.0|^8.0", + "illuminate/console": "^6.0|^7.0|^8.0|^9.0", + "illuminate/support": "^6.0|^7.0|^8.0|^9.0", "nesbot/carbon": "^2.0", "php": "^7.2|^8.0", "php-webdriver/webdriver": "^1.9.0", - "symfony/console": "^4.3|^5.0", - "symfony/finder": "^4.3|^5.0", - "symfony/process": "^4.3|^5.0", - "vlucas/phpdotenv": "^3.0|^4.0|^5.0" + "symfony/console": "^4.3|^5.0|^6.0", + "symfony/finder": "^4.3|^5.0|^6.0", + "symfony/process": "^4.3|^5.0|^6.0", + "vlucas/phpdotenv": "^3.0|^4.0|^5.2" }, "require-dev": { "mockery/mockery": "^1.0", - "orchestra/testbench": "^4.16|^5.17.1|^6.12.1", + "orchestra/testbench": "^4.16|^5.17.1|^6.12.1|^7.0", "phpunit/phpunit": "^7.5.15|^8.4|^9.0" }, "suggest": { @@ -8078,31 +8421,32 @@ ], "support": { "issues": "https://github.com/laravel/dusk/issues", - "source": "https://github.com/laravel/dusk/tree/v6.15.0" + "source": "https://github.com/laravel/dusk/tree/v6.23.1" }, - "time": "2021-04-06T14:14:57+00:00" + "time": "2022-05-02T14:01:47+00:00" }, { "name": "maximebf/debugbar", - "version": "v1.16.5", + "version": "v1.18.0", "source": { "type": "git", "url": "https://github.com/maximebf/php-debugbar.git", - "reference": "6d51ee9e94cff14412783785e79a4e7ef97b9d62" + "reference": "0d44b75f3b5d6d41ae83b79c7a4bceae7fbc78b6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/6d51ee9e94cff14412783785e79a4e7ef97b9d62", - "reference": "6d51ee9e94cff14412783785e79a4e7ef97b9d62", + "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/0d44b75f3b5d6d41ae83b79c7a4bceae7fbc78b6", + "reference": "0d44b75f3b5d6d41ae83b79c7a4bceae7fbc78b6", "shasum": "" }, "require": { "php": "^7.1|^8", - "psr/log": "^1.0", - "symfony/var-dumper": "^2.6|^3|^4|^5" + "psr/log": "^1|^2|^3", + "symfony/var-dumper": "^2.6|^3|^4|^5|^6" }, "require-dev": { - "phpunit/phpunit": "^7.5.20 || ^9.4.2" + "phpunit/phpunit": "^7.5.20 || ^9.4.2", + "twig/twig": "^1.38|^2.7|^3.0" }, "suggest": { "kriswallsmith/assetic": "The best way to manage assets", @@ -8112,7 +8456,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.16-dev" + "dev-master": "1.17-dev" } }, "autoload": { @@ -8143,22 +8487,22 @@ ], "support": { "issues": "https://github.com/maximebf/php-debugbar/issues", - "source": "https://github.com/maximebf/php-debugbar/tree/v1.16.5" + "source": "https://github.com/maximebf/php-debugbar/tree/v1.18.0" }, - "time": "2020-12-07T11:07:24+00:00" + "time": "2021-12-27T18:49:48+00:00" }, { "name": "mockery/mockery", - "version": "1.4.3", + "version": "1.5.0", "source": { "type": "git", "url": "https://github.com/mockery/mockery.git", - "reference": "d1339f64479af1bee0e82a0413813fe5345a54ea" + "reference": "c10a5f6e06fc2470ab1822fa13fa2a7380f8fbac" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/mockery/mockery/zipball/d1339f64479af1bee0e82a0413813fe5345a54ea", - "reference": "d1339f64479af1bee0e82a0413813fe5345a54ea", + "url": "https://api.github.com/repos/mockery/mockery/zipball/c10a5f6e06fc2470ab1822fa13fa2a7380f8fbac", + "reference": "c10a5f6e06fc2470ab1822fa13fa2a7380f8fbac", "shasum": "" }, "require": { @@ -8215,43 +8559,44 @@ ], "support": { "issues": "https://github.com/mockery/mockery/issues", - "source": "https://github.com/mockery/mockery/tree/1.4.3" + "source": "https://github.com/mockery/mockery/tree/1.5.0" }, - "time": "2021-02-24T09:51:49+00:00" + "time": "2022-01-20T13:18:17+00:00" }, { "name": "myclabs/deep-copy", - "version": "1.10.2", + "version": "1.11.0", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220" + "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/776f831124e9c62e1a2c601ecc52e776d8bb7220", - "reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/14daed4296fae74d9e3201d2c4925d1acb7aa614", + "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614", "shasum": "" }, "require": { "php": "^7.1 || ^8.0" }, - "replace": { - "myclabs/deep-copy": "self.version" + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3,<3.2.2" }, "require-dev": { - "doctrine/collections": "^1.0", - "doctrine/common": "^2.6", - "phpunit/phpunit": "^7.1" + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" }, "type": "library", "autoload": { - "psr-4": { - "DeepCopy\\": "src/DeepCopy/" - }, "files": [ "src/DeepCopy/deep_copy.php" - ] + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -8267,7 +8612,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.10.2" + "source": "https://github.com/myclabs/DeepCopy/tree/1.11.0" }, "funding": [ { @@ -8275,37 +8620,36 @@ "type": "tidelift" } ], - "time": "2020-11-13T09:40:50+00:00" + "time": "2022-03-03T13:19:32+00:00" }, { "name": "nunomaduro/collision", - "version": "v5.4.0", + "version": "v5.11.0", "source": { "type": "git", "url": "https://github.com/nunomaduro/collision.git", - "reference": "41b7e9999133d5082700d31a1d0977161df8322a" + "reference": "8b610eef8582ccdc05d8f2ab23305e2d37049461" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/collision/zipball/41b7e9999133d5082700d31a1d0977161df8322a", - "reference": "41b7e9999133d5082700d31a1d0977161df8322a", + "url": "https://api.github.com/repos/nunomaduro/collision/zipball/8b610eef8582ccdc05d8f2ab23305e2d37049461", + "reference": "8b610eef8582ccdc05d8f2ab23305e2d37049461", "shasum": "" }, "require": { "facade/ignition-contracts": "^1.0", - "filp/whoops": "^2.7.2", + "filp/whoops": "^2.14.3", "php": "^7.3 || ^8.0", "symfony/console": "^5.0" }, "require-dev": { "brianium/paratest": "^6.1", "fideloper/proxy": "^4.4.1", - "friendsofphp/php-cs-fixer": "^2.17.3", "fruitcake/laravel-cors": "^2.0.3", - "laravel/framework": "^9.0", + "laravel/framework": "8.x-dev", "nunomaduro/larastan": "^0.6.2", "nunomaduro/mock-final-classes": "^1.0", - "orchestra/testbench": "^7.0", + "orchestra/testbench": "^6.0", "phpstan/phpstan": "^0.12.64", "phpunit/phpunit": "^9.5.0" }, @@ -8351,7 +8695,7 @@ }, "funding": [ { - "url": "https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=66BYDWAT92N6L", + "url": "https://www.paypal.com/paypalme/enunomaduro", "type": "custom" }, { @@ -8363,20 +8707,20 @@ "type": "patreon" } ], - "time": "2021-04-09T13:38:32+00:00" + "time": "2022-01-10T16:22:52+00:00" }, { "name": "phar-io/manifest", - "version": "2.0.1", + "version": "2.0.3", "source": { "type": "git", "url": "https://github.com/phar-io/manifest.git", - "reference": "85265efd3af7ba3ca4b2a2c34dbfc5788dd29133" + "reference": "97803eca37d319dfa7826cc2437fc020857acb53" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/manifest/zipball/85265efd3af7ba3ca4b2a2c34dbfc5788dd29133", - "reference": "85265efd3af7ba3ca4b2a2c34dbfc5788dd29133", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53", + "reference": "97803eca37d319dfa7826cc2437fc020857acb53", "shasum": "" }, "require": { @@ -8421,22 +8765,22 @@ "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", "support": { "issues": "https://github.com/phar-io/manifest/issues", - "source": "https://github.com/phar-io/manifest/tree/master" + "source": "https://github.com/phar-io/manifest/tree/2.0.3" }, - "time": "2020-06-27T14:33:11+00:00" + "time": "2021-07-20T11:28:43+00:00" }, { "name": "phar-io/version", - "version": "3.1.0", + "version": "3.2.1", "source": { "type": "git", "url": "https://github.com/phar-io/version.git", - "reference": "bae7c545bef187884426f042434e561ab1ddb182" + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/version/zipball/bae7c545bef187884426f042434e561ab1ddb182", - "reference": "bae7c545bef187884426f042434e561ab1ddb182", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", "shasum": "" }, "require": { @@ -8472,9 +8816,9 @@ "description": "Library for handling version information and constraints", "support": { "issues": "https://github.com/phar-io/version/issues", - "source": "https://github.com/phar-io/version/tree/3.1.0" + "source": "https://github.com/phar-io/version/tree/3.2.1" }, - "time": "2021-02-23T14:00:09+00:00" + "time": "2022-02-21T01:04:05+00:00" }, { "name": "php-cs-fixer/diff", @@ -8533,16 +8877,16 @@ }, { "name": "php-mock/php-mock", - "version": "2.3.0", + "version": "2.3.1", "source": { "type": "git", "url": "https://github.com/php-mock/php-mock.git", - "reference": "a3142f257153b71c09bf9146ecf73430b3818b7c" + "reference": "9a55bd8ba40e6da2e97a866121d2c69dedd4952b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-mock/php-mock/zipball/a3142f257153b71c09bf9146ecf73430b3818b7c", - "reference": "a3142f257153b71c09bf9146ecf73430b3818b7c", + "url": "https://api.github.com/repos/php-mock/php-mock/zipball/9a55bd8ba40e6da2e97a866121d2c69dedd4952b", + "reference": "9a55bd8ba40e6da2e97a866121d2c69dedd4952b", "shasum": "" }, "require": { @@ -8596,7 +8940,7 @@ ], "support": { "issues": "https://github.com/php-mock/php-mock/issues", - "source": "https://github.com/php-mock/php-mock/tree/2.3.0" + "source": "https://github.com/php-mock/php-mock/tree/2.3.1" }, "funding": [ { @@ -8604,7 +8948,7 @@ "type": "github" } ], - "time": "2020-12-11T19:20:04+00:00" + "time": "2022-02-07T18:57:52+00:00" }, { "name": "php-mock/php-mock-integration", @@ -8723,16 +9067,16 @@ }, { "name": "php-webdriver/webdriver", - "version": "1.11.1", + "version": "1.12.1", "source": { "type": "git", "url": "https://github.com/php-webdriver/php-webdriver.git", - "reference": "da16e39968f8dd5cfb7d07eef91dc2b731c69880" + "reference": "b27ddf458d273c7d4602106fcaf978aa0b7fe15a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-webdriver/php-webdriver/zipball/da16e39968f8dd5cfb7d07eef91dc2b731c69880", - "reference": "da16e39968f8dd5cfb7d07eef91dc2b731c69880", + "url": "https://api.github.com/repos/php-webdriver/php-webdriver/zipball/b27ddf458d273c7d4602106fcaf978aa0b7fe15a", + "reference": "b27ddf458d273c7d4602106fcaf978aa0b7fe15a", "shasum": "" }, "require": { @@ -8741,32 +9085,31 @@ "ext-zip": "*", "php": "^5.6 || ~7.0 || ^8.0", "symfony/polyfill-mbstring": "^1.12", - "symfony/process": "^2.8 || ^3.1 || ^4.0 || ^5.0" + "symfony/process": "^2.8 || ^3.1 || ^4.0 || ^5.0 || ^6.0" }, "replace": { "facebook/webdriver": "*" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^2.0", "ondram/ci-detector": "^2.1 || ^3.5 || ^4.0", "php-coveralls/php-coveralls": "^2.4", "php-mock/php-mock-phpunit": "^1.1 || ^2.0", "php-parallel-lint/php-parallel-lint": "^1.2", "phpunit/phpunit": "^5.7 || ^7 || ^8 || ^9", "squizlabs/php_codesniffer": "^3.5", - "symfony/var-dumper": "^3.3 || ^4.0 || ^5.0" + "symfony/var-dumper": "^3.3 || ^4.0 || ^5.0 || ^6.0" }, "suggest": { "ext-SimpleXML": "For Firefox profile creation" }, "type": "library", "autoload": { - "psr-4": { - "Facebook\\WebDriver\\": "lib/" - }, "files": [ "lib/Exception/TimeoutException.php" - ] + ], + "psr-4": { + "Facebook\\WebDriver\\": "lib/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -8783,9 +9126,9 @@ ], "support": { "issues": "https://github.com/php-webdriver/php-webdriver/issues", - "source": "https://github.com/php-webdriver/php-webdriver/tree/1.11.1" + "source": "https://github.com/php-webdriver/php-webdriver/tree/1.12.1" }, - "time": "2021-05-21T15:12:49+00:00" + "time": "2022-05-03T12:16:34+00:00" }, { "name": "phpdocumentor/reflection-common", @@ -8842,16 +9185,16 @@ }, { "name": "phpdocumentor/reflection-docblock", - "version": "5.2.2", + "version": "5.3.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556" + "reference": "622548b623e81ca6d78b721c5e029f4ce664f170" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/069a785b2141f5bcf49f3e353548dc1cce6df556", - "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/622548b623e81ca6d78b721c5e029f4ce664f170", + "reference": "622548b623e81ca6d78b721c5e029f4ce664f170", "shasum": "" }, "require": { @@ -8862,7 +9205,8 @@ "webmozart/assert": "^1.9.1" }, "require-dev": { - "mockery/mockery": "~1.3.2" + "mockery/mockery": "~1.3.2", + "psalm/phar": "^4.8" }, "type": "library", "extra": { @@ -8892,22 +9236,22 @@ "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", "support": { "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", - "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/master" + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.3.0" }, - "time": "2020-09-03T19:13:55+00:00" + "time": "2021-10-19T17:43:47+00:00" }, { "name": "phpdocumentor/type-resolver", - "version": "1.4.0", + "version": "1.6.1", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0" + "reference": "77a32518733312af16a44300404e945338981de3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0", - "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/77a32518733312af16a44300404e945338981de3", + "reference": "77a32518733312af16a44300404e945338981de3", "shasum": "" }, "require": { @@ -8915,7 +9259,8 @@ "phpdocumentor/reflection-common": "^2.0" }, "require-dev": { - "ext-tokenizer": "*" + "ext-tokenizer": "*", + "psalm/phar": "^4.8" }, "type": "library", "extra": { @@ -8941,39 +9286,39 @@ "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", "support": { "issues": "https://github.com/phpDocumentor/TypeResolver/issues", - "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.4.0" + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.6.1" }, - "time": "2020-09-17T18:55:26+00:00" + "time": "2022-03-15T21:29:03+00:00" }, { "name": "phpspec/prophecy", - "version": "1.13.0", + "version": "v1.15.0", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "be1996ed8adc35c3fd795488a653f4b518be70ea" + "reference": "bbcd7380b0ebf3961ee21409db7b38bc31d69a13" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/be1996ed8adc35c3fd795488a653f4b518be70ea", - "reference": "be1996ed8adc35c3fd795488a653f4b518be70ea", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/bbcd7380b0ebf3961ee21409db7b38bc31d69a13", + "reference": "bbcd7380b0ebf3961ee21409db7b38bc31d69a13", "shasum": "" }, "require": { "doctrine/instantiator": "^1.2", - "php": "^7.2 || ~8.0, <8.1", + "php": "^7.2 || ~8.0, <8.2", "phpdocumentor/reflection-docblock": "^5.2", "sebastian/comparator": "^3.0 || ^4.0", "sebastian/recursion-context": "^3.0 || ^4.0" }, "require-dev": { - "phpspec/phpspec": "^6.0", + "phpspec/phpspec": "^6.0 || ^7.0", "phpunit/phpunit": "^8.0 || ^9.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.11.x-dev" + "dev-master": "1.x-dev" } }, "autoload": { @@ -9008,29 +9353,29 @@ ], "support": { "issues": "https://github.com/phpspec/prophecy/issues", - "source": "https://github.com/phpspec/prophecy/tree/1.13.0" + "source": "https://github.com/phpspec/prophecy/tree/v1.15.0" }, - "time": "2021-03-17T13:42:18+00:00" + "time": "2021-12-08T12:19:24+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "9.2.6", + "version": "9.2.15", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "f6293e1b30a2354e8428e004689671b83871edde" + "reference": "2e9da11878c4202f97915c1cb4bb1ca318a63f5f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/f6293e1b30a2354e8428e004689671b83871edde", - "reference": "f6293e1b30a2354e8428e004689671b83871edde", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/2e9da11878c4202f97915c1cb4bb1ca318a63f5f", + "reference": "2e9da11878c4202f97915c1cb4bb1ca318a63f5f", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^4.10.2", + "nikic/php-parser": "^4.13.0", "php": ">=7.3", "phpunit/php-file-iterator": "^3.0.3", "phpunit/php-text-template": "^2.0.2", @@ -9079,7 +9424,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.6" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.15" }, "funding": [ { @@ -9087,20 +9432,20 @@ "type": "github" } ], - "time": "2021-03-28T07:26:59+00:00" + "time": "2022-03-07T09:28:20+00:00" }, { "name": "phpunit/php-file-iterator", - "version": "3.0.5", + "version": "3.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "aa4be8575f26070b100fccb67faabb28f21f66f8" + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/aa4be8575f26070b100fccb67faabb28f21f66f8", - "reference": "aa4be8575f26070b100fccb67faabb28f21f66f8", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", "shasum": "" }, "require": { @@ -9139,7 +9484,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.5" + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6" }, "funding": [ { @@ -9147,7 +9492,7 @@ "type": "github" } ], - "time": "2020-09-28T05:57:25+00:00" + "time": "2021-12-02T12:48:52+00:00" }, { "name": "phpunit/php-invoker", @@ -9332,16 +9677,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.5.5", + "version": "9.5.20", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "89ff45ea9d70e35522fb6654a2ebc221158de276" + "reference": "12bc8879fb65aef2138b26fc633cb1e3620cffba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/89ff45ea9d70e35522fb6654a2ebc221158de276", - "reference": "89ff45ea9d70e35522fb6654a2ebc221158de276", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/12bc8879fb65aef2138b26fc633cb1e3620cffba", + "reference": "12bc8879fb65aef2138b26fc633cb1e3620cffba", "shasum": "" }, "require": { @@ -9353,11 +9698,11 @@ "ext-xml": "*", "ext-xmlwriter": "*", "myclabs/deep-copy": "^1.10.1", - "phar-io/manifest": "^2.0.1", + "phar-io/manifest": "^2.0.3", "phar-io/version": "^3.0.2", "php": ">=7.3", "phpspec/prophecy": "^1.12.1", - "phpunit/php-code-coverage": "^9.2.3", + "phpunit/php-code-coverage": "^9.2.13", "phpunit/php-file-iterator": "^3.0.5", "phpunit/php-invoker": "^3.1.1", "phpunit/php-text-template": "^2.0.3", @@ -9371,7 +9716,7 @@ "sebastian/global-state": "^5.0.1", "sebastian/object-enumerator": "^4.0.3", "sebastian/resource-operations": "^3.0.3", - "sebastian/type": "^2.3.2", + "sebastian/type": "^3.0", "sebastian/version": "^3.0.2" }, "require-dev": { @@ -9392,11 +9737,11 @@ } }, "autoload": { - "classmap": [ - "src/" - ], "files": [ "src/Framework/Assert/Functions.php" + ], + "classmap": [ + "src/" ] }, "notification-url": "https://packagist.org/downloads/", @@ -9419,11 +9764,11 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.5" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.20" }, "funding": [ { - "url": "https://phpunit.de/donate.html", + "url": "https://phpunit.de/sponsors.html", "type": "custom" }, { @@ -9431,57 +9776,7 @@ "type": "github" } ], - "time": "2021-06-05T04:49:07+00:00" - }, - { - "name": "react/promise", - "version": "v2.8.0", - "source": { - "type": "git", - "url": "https://github.com/reactphp/promise.git", - "reference": "f3cff96a19736714524ca0dd1d4130de73dbbbc4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/reactphp/promise/zipball/f3cff96a19736714524ca0dd1d4130de73dbbbc4", - "reference": "f3cff96a19736714524ca0dd1d4130de73dbbbc4", - "shasum": "" - }, - "require": { - "php": ">=5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "^7.0 || ^6.5 || ^5.7 || ^4.8.36" - }, - "type": "library", - "autoload": { - "psr-4": { - "React\\Promise\\": "src/" - }, - "files": [ - "src/functions_include.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jan Sorgalla", - "email": "jsorgalla@gmail.com" - } - ], - "description": "A lightweight implementation of CommonJS Promises/A for PHP", - "keywords": [ - "promise", - "promises" - ], - "support": { - "issues": "https://github.com/reactphp/promise/issues", - "source": "https://github.com/reactphp/promise/tree/v2.8.0" - }, - "time": "2020-05-12T15:16:56+00:00" + "time": "2022-04-01T12:37:26+00:00" }, { "name": "sebastian/cli-parser", @@ -9849,16 +10144,16 @@ }, { "name": "sebastian/environment", - "version": "5.1.3", + "version": "5.1.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "388b6ced16caa751030f6a69e588299fa09200ac" + "reference": "1b5dff7bb151a4db11d49d90e5408e4e938270f7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/388b6ced16caa751030f6a69e588299fa09200ac", - "reference": "388b6ced16caa751030f6a69e588299fa09200ac", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/1b5dff7bb151a4db11d49d90e5408e4e938270f7", + "reference": "1b5dff7bb151a4db11d49d90e5408e4e938270f7", "shasum": "" }, "require": { @@ -9900,7 +10195,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/environment/issues", - "source": "https://github.com/sebastianbergmann/environment/tree/5.1.3" + "source": "https://github.com/sebastianbergmann/environment/tree/5.1.4" }, "funding": [ { @@ -9908,20 +10203,20 @@ "type": "github" } ], - "time": "2020-09-28T05:52:38+00:00" + "time": "2022-04-03T09:37:03+00:00" }, { "name": "sebastian/exporter", - "version": "4.0.3", + "version": "4.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "d89cc98761b8cb5a1a235a6b703ae50d34080e65" + "reference": "65e8b7db476c5dd267e65eea9cab77584d3cfff9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/d89cc98761b8cb5a1a235a6b703ae50d34080e65", - "reference": "d89cc98761b8cb5a1a235a6b703ae50d34080e65", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/65e8b7db476c5dd267e65eea9cab77584d3cfff9", + "reference": "65e8b7db476c5dd267e65eea9cab77584d3cfff9", "shasum": "" }, "require": { @@ -9970,14 +10265,14 @@ } ], "description": "Provides the functionality to export PHP variables for visualization", - "homepage": "http://www.github.com/sebastianbergmann/exporter", + "homepage": "https://www.github.com/sebastianbergmann/exporter", "keywords": [ "export", "exporter" ], "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", - "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.3" + "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.4" }, "funding": [ { @@ -9985,20 +10280,20 @@ "type": "github" } ], - "time": "2020-09-28T05:24:23+00:00" + "time": "2021-11-11T14:18:36+00:00" }, { "name": "sebastian/global-state", - "version": "5.0.3", + "version": "5.0.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "23bd5951f7ff26f12d4e3242864df3e08dec4e49" + "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/23bd5951f7ff26f12d4e3242864df3e08dec4e49", - "reference": "23bd5951f7ff26f12d4e3242864df3e08dec4e49", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/0ca8db5a5fc9c8646244e629625ac486fa286bf2", + "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2", "shasum": "" }, "require": { @@ -10041,7 +10336,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/global-state/issues", - "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.3" + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.5" }, "funding": [ { @@ -10049,7 +10344,7 @@ "type": "github" } ], - "time": "2021-06-11T13:31:12+00:00" + "time": "2022-02-14T08:28:10+00:00" }, { "name": "sebastian/lines-of-code", @@ -10336,33 +10631,32 @@ "type": "github" } ], - "abandoned": true, "time": "2020-09-28T06:45:17+00:00" }, { "name": "sebastian/type", - "version": "2.3.4", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", - "reference": "b8cd8a1c753c90bc1a0f5372170e3e489136f914" + "reference": "b233b84bc4465aff7b57cf1c4bc75c86d00d6dad" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/b8cd8a1c753c90bc1a0f5372170e3e489136f914", - "reference": "b8cd8a1c753c90bc1a0f5372170e3e489136f914", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/b233b84bc4465aff7b57cf1c4bc75c86d00d6dad", + "reference": "b233b84bc4465aff7b57cf1c4bc75c86d00d6dad", "shasum": "" }, "require": { "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^9.5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.3-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -10385,7 +10679,7 @@ "homepage": "https://github.com/sebastianbergmann/type", "support": { "issues": "https://github.com/sebastianbergmann/type/issues", - "source": "https://github.com/sebastianbergmann/type/tree/2.3.4" + "source": "https://github.com/sebastianbergmann/type/tree/3.0.0" }, "funding": [ { @@ -10393,7 +10687,7 @@ "type": "github" } ], - "time": "2021-06-15T12:49:02+00:00" + "time": "2022-03-15T09:54:48+00:00" }, { "name": "sebastian/version", @@ -10448,135 +10742,23 @@ ], "time": "2020-09-28T06:39:44+00:00" }, - { - "name": "seld/jsonlint", - "version": "1.8.3", - "source": { - "type": "git", - "url": "https://github.com/Seldaek/jsonlint.git", - "reference": "9ad6ce79c342fbd44df10ea95511a1b24dee5b57" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/9ad6ce79c342fbd44df10ea95511a1b24dee5b57", - "reference": "9ad6ce79c342fbd44df10ea95511a1b24dee5b57", - "shasum": "" - }, - "require": { - "php": "^5.3 || ^7.0 || ^8.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" - }, - "bin": [ - "bin/jsonlint" - ], - "type": "library", - "autoload": { - "psr-4": { - "Seld\\JsonLint\\": "src/Seld/JsonLint/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" - } - ], - "description": "JSON Linter", - "keywords": [ - "json", - "linter", - "parser", - "validator" - ], - "support": { - "issues": "https://github.com/Seldaek/jsonlint/issues", - "source": "https://github.com/Seldaek/jsonlint/tree/1.8.3" - }, - "funding": [ - { - "url": "https://github.com/Seldaek", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/seld/jsonlint", - "type": "tidelift" - } - ], - "time": "2020-11-11T09:19:24+00:00" - }, - { - "name": "seld/phar-utils", - "version": "1.1.1", - "source": { - "type": "git", - "url": "https://github.com/Seldaek/phar-utils.git", - "reference": "8674b1d84ffb47cc59a101f5d5a3b61e87d23796" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Seldaek/phar-utils/zipball/8674b1d84ffb47cc59a101f5d5a3b61e87d23796", - "reference": "8674b1d84ffb47cc59a101f5d5a3b61e87d23796", - "shasum": "" - }, - "require": { - "php": ">=5.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Seld\\PharUtils\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be" - } - ], - "description": "PHAR file format utilities, for when PHP phars you up", - "keywords": [ - "phar" - ], - "support": { - "issues": "https://github.com/Seldaek/phar-utils/issues", - "source": "https://github.com/Seldaek/phar-utils/tree/master" - }, - "time": "2020-07-07T18:42:57+00:00" - }, { "name": "symfony/debug", - "version": "v4.4.25", + "version": "v4.4.41", "source": { "type": "git", "url": "https://github.com/symfony/debug.git", - "reference": "a8d2d5c94438548bff9f998ca874e202bb29d07f" + "reference": "6637e62480b60817b9a6984154a533e8e64c6bd5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/debug/zipball/a8d2d5c94438548bff9f998ca874e202bb29d07f", - "reference": "a8d2d5c94438548bff9f998ca874e202bb29d07f", + "url": "https://api.github.com/repos/symfony/debug/zipball/6637e62480b60817b9a6984154a533e8e64c6bd5", + "reference": "6637e62480b60817b9a6984154a533e8e64c6bd5", "shasum": "" }, "require": { "php": ">=7.1.3", - "psr/log": "~1.0", - "symfony/polyfill-php80": "^1.15" + "psr/log": "^1|^2|^3" }, "conflict": { "symfony/http-kernel": "<3.4" @@ -10610,7 +10792,7 @@ "description": "Provides tools to ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/debug/tree/v4.4.25" + "source": "https://github.com/symfony/debug/tree/v4.4.41" }, "funding": [ { @@ -10626,25 +10808,27 @@ "type": "tidelift" } ], - "time": "2021-05-26T17:39:37+00:00" + "time": "2022-04-12T15:19:55+00:00" }, { "name": "symfony/filesystem", - "version": "v5.3.0", + "version": "v5.4.7", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "348116319d7fb7d1faa781d26a48922428013eb2" + "reference": "3a4442138d80c9f7b600fb297534ac718b61d37f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/348116319d7fb7d1faa781d26a48922428013eb2", - "reference": "348116319d7fb7d1faa781d26a48922428013eb2", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/3a4442138d80c9f7b600fb297534ac718b61d37f", + "reference": "3a4442138d80c9f7b600fb297534ac718b61d37f", "shasum": "" }, "require": { "php": ">=7.2.5", - "symfony/polyfill-ctype": "~1.8" + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8", + "symfony/polyfill-php80": "^1.16" }, "type": "library", "autoload": { @@ -10672,7 +10856,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v5.3.0" + "source": "https://github.com/symfony/filesystem/tree/v5.4.7" }, "funding": [ { @@ -10688,27 +10872,27 @@ "type": "tidelift" } ], - "time": "2021-05-26T17:43:10+00:00" + "time": "2022-04-01T12:33:59+00:00" }, { "name": "symfony/options-resolver", - "version": "v5.3.0", + "version": "v5.4.3", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "162e886ca035869866d233a2bfef70cc28f9bbe5" + "reference": "cc1147cb11af1b43f503ac18f31aa3bec213aba8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/162e886ca035869866d233a2bfef70cc28f9bbe5", - "reference": "162e886ca035869866d233a2bfef70cc28f9bbe5", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/cc1147cb11af1b43f503ac18f31aa3bec213aba8", + "reference": "cc1147cb11af1b43f503ac18f31aa3bec213aba8", "shasum": "" }, "require": { "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1", + "symfony/deprecation-contracts": "^2.1|^3", "symfony/polyfill-php73": "~1.0", - "symfony/polyfill-php80": "^1.15" + "symfony/polyfill-php80": "^1.16" }, "type": "library", "autoload": { @@ -10741,7 +10925,7 @@ "options" ], "support": { - "source": "https://github.com/symfony/options-resolver/tree/v5.3.0" + "source": "https://github.com/symfony/options-resolver/tree/v5.4.3" }, "funding": [ { @@ -10757,7 +10941,7 @@ "type": "tidelift" } ], - "time": "2021-05-26T17:43:10+00:00" + "time": "2022-01-02T09:53:40+00:00" }, { "name": "symfony/polyfill-php70", @@ -10829,21 +11013,21 @@ }, { "name": "symfony/stopwatch", - "version": "v5.3.0", + "version": "v5.4.5", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "313d02f59d6543311865007e5ff4ace05b35ee65" + "reference": "4d04b5c24f3c9a1a168a131f6cbe297155bc0d30" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/313d02f59d6543311865007e5ff4ace05b35ee65", - "reference": "313d02f59d6543311865007e5ff4ace05b35ee65", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/4d04b5c24f3c9a1a168a131f6cbe297155bc0d30", + "reference": "4d04b5c24f3c9a1a168a131f6cbe297155bc0d30", "shasum": "" }, "require": { "php": ">=7.2.5", - "symfony/service-contracts": "^1.0|^2" + "symfony/service-contracts": "^1|^2|^3" }, "type": "library", "autoload": { @@ -10871,7 +11055,7 @@ "description": "Provides a way to profile code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/stopwatch/tree/v5.3.0" + "source": "https://github.com/symfony/stopwatch/tree/v5.4.5" }, "funding": [ { @@ -10887,20 +11071,20 @@ "type": "tidelift" } ], - "time": "2021-05-26T17:43:10+00:00" + "time": "2022-02-18T16:06:09+00:00" }, { "name": "theseer/tokenizer", - "version": "1.2.0", + "version": "1.2.1", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "75a63c33a8577608444246075ea0af0d052e452a" + "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/75a63c33a8577608444246075ea0af0d052e452a", - "reference": "75a63c33a8577608444246075ea0af0d052e452a", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", + "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", "shasum": "" }, "require": { @@ -10929,7 +11113,7 @@ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "support": { "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/master" + "source": "https://github.com/theseer/tokenizer/tree/1.2.1" }, "funding": [ { @@ -10937,7 +11121,7 @@ "type": "github" } ], - "time": "2020-07-12T23:59:07+00:00" + "time": "2021-07-28T10:34:58+00:00" } ], "aliases": [], @@ -10946,12 +11130,13 @@ "prefer-stable": true, "prefer-lowest": false, "platform": { - "php": "^7.4 || ^8.0", + "php": "^7.4 || ^8.0 || ^8.1", "ext-json": "*", "ext-mbstring": "*", + "ext-pdo": "*", "ext-pdo_mysql": "*", "ext-zip": "*" }, "platform-dev": [], - "plugin-api-version": "2.1.0" + "plugin-api-version": "2.3.0" } From 3e55a79439bc91071a7ef690744d9d68b05149e8 Mon Sep 17 00:00:00 2001 From: DaneEveritt Date: Wed, 4 May 2022 17:48:09 -0400 Subject: [PATCH 002/458] Don't default to redis on the first runs, causes errors when trying to setup environment --- .env.example | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.env.example b/.env.example index e2e9d2dce4..97f68f78a4 100644 --- a/.env.example +++ b/.env.example @@ -16,6 +16,9 @@ DB_DATABASE=panel DB_USERNAME=pterodactyl DB_PASSWORD= +SESSION_DRIVER=file +CACHE_DRIVER=file + HASHIDS_SALT= HASHIDS_LENGTH=8 From 34ffaebd3e2a57397f0c35aebd0fd589a757898f Mon Sep 17 00:00:00 2001 From: DaneEveritt Date: Wed, 4 May 2022 19:01:29 -0400 Subject: [PATCH 003/458] Run cs-fix, ensure we only install dependency versions supporting 7.4+ --- .gitignore | 11 +- .php-cs-fixer.dist.php | 2 +- .../Environment/AppSettingsCommand.php | 1 + app/Console/Commands/Node/MakeNodeCommand.php | 10 +- .../Schedule/ProcessRunnableCommand.php | 2 +- app/Console/Commands/UpgradeCommand.php | 1 + .../Servers/ServerTransferController.php | 2 - .../Auth/AbstractLoginController.php | 3 - .../Auth/LoginCheckpointController.php | 5 +- app/Http/Middleware/VerifyCsrfToken.php | 2 +- .../Servers/BuildModificationService.php | 4 +- composer.json | 8 +- composer.lock | 383 ++++++------------ config/debugbar.php | 2 +- .../Location/LocationControllerTest.php | 6 +- .../DeployServerDatabaseServiceTest.php | 2 +- 16 files changed, 154 insertions(+), 290 deletions(-) diff --git a/.gitignore b/.gitignore index 658743e223..2ffab75293 100644 --- a/.gitignore +++ b/.gitignore @@ -15,26 +15,17 @@ node_modules _ide_helper.php _ide_helper_models.php .phpstorm.meta.php -.php_cs.cache .yarn public/assets/manifest.json # For local development with docker # Remove if we ever put the Dockerfile in the repo .dockerignore -#Dockerfile docker-compose.yml # for image related files misc -.phpstorm.meta.php -.php_cs.cache - +.php-cs-fixer.cache coverage.xml - -# Vagrant -*.log resources/lang/locales.js -resources/assets/pterodactyl/scripts/helpers/ziggy.js -resources/assets/scripts/helpers/ziggy.js .phpunit.result.cache diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index 7c7676810f..9245b7e6e5 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -29,7 +29,7 @@ 'no_unreachable_default_argument_value' => true, 'no_useless_return' => true, 'ordered_imports' => [ - 'sortAlgorithm' => 'length', + 'sort_algorithm' => 'length', ], 'phpdoc_align' => [ 'align' => 'left', diff --git a/app/Console/Commands/Environment/AppSettingsCommand.php b/app/Console/Commands/Environment/AppSettingsCommand.php index 2746d1a4f4..59784e0f36 100644 --- a/app/Console/Commands/Environment/AppSettingsCommand.php +++ b/app/Console/Commands/Environment/AppSettingsCommand.php @@ -114,6 +114,7 @@ public function handle() foreach ($validator->errors()->all() as $error) { $this->output->error($error); } + return 1; } diff --git a/app/Console/Commands/Node/MakeNodeCommand.php b/app/Console/Commands/Node/MakeNodeCommand.php index c70aef35fb..932e549ac2 100644 --- a/app/Console/Commands/Node/MakeNodeCommand.php +++ b/app/Console/Commands/Node/MakeNodeCommand.php @@ -46,7 +46,6 @@ class MakeNodeCommand extends Command */ protected $description = 'Creates a new node on the system via the CLI.'; - /** * Handle the command execution process. * @@ -62,13 +61,18 @@ public function handle(NodeCreationService $creationService) $data['fqdn'] = $this->option('fqdn') ?? $this->ask('Enter a domain name (e.g node.example.com) to be used for connecting to the daemon. An IP address may only be used if you are not using SSL for this node'); if (!filter_var(gethostbyname($data['fqdn']), FILTER_VALIDATE_IP)) { $this->error('The FQDN or IP address provided does not resolve to a valid IP address.'); + return; } $data['public'] = $this->option('public') ?? $this->confirm('Should this node be public? As a note, setting a node to private you will be denying the ability to auto-deploy to this node.', true); - $data['scheme'] = $this->option('scheme') ?? $this->anticipate('Please either enter https for SSL or http for a non-ssl connection', - ["https","http",],"https"); + $data['scheme'] = $this->option('scheme') ?? $this->anticipate( + 'Please either enter https for SSL or http for a non-ssl connection', + ['https', 'http'], + 'https' + ); if (filter_var($data['fqdn'], FILTER_VALIDATE_IP) && $data['scheme'] === 'https') { $this->error('A fully qualified domain name that resolves to a public IP address is required in order to use SSL for this node.'); + return; } $data['behind_proxy'] = $this->option('proxy') ?? $this->confirm('Is your FQDN behind a proxy?'); diff --git a/app/Console/Commands/Schedule/ProcessRunnableCommand.php b/app/Console/Commands/Schedule/ProcessRunnableCommand.php index 9e51feaeb0..fa335c2bd5 100644 --- a/app/Console/Commands/Schedule/ProcessRunnableCommand.php +++ b/app/Console/Commands/Schedule/ProcessRunnableCommand.php @@ -69,7 +69,7 @@ protected function processSchedule(Schedule $schedule) 'schedule' => $schedule->name, 'hash' => $schedule->hashid, ])); - } catch (Throwable | Exception $exception) { + } catch (Throwable|Exception $exception) { Log::error($exception, ['schedule_id' => $schedule->id]); $this->error("An error was encountered while processing Schedule #{$schedule->id}: " . $exception->getMessage()); diff --git a/app/Console/Commands/UpgradeCommand.php b/app/Console/Commands/UpgradeCommand.php index 2243298696..86b2ed5f99 100644 --- a/app/Console/Commands/UpgradeCommand.php +++ b/app/Console/Commands/UpgradeCommand.php @@ -87,6 +87,7 @@ public function handle() if (!$this->confirm('Are you sure you want to run the upgrade process for your Panel?')) { $this->warn('Upgrade process terminated by user.'); + return; } } diff --git a/app/Http/Controllers/Api/Remote/Servers/ServerTransferController.php b/app/Http/Controllers/Api/Remote/Servers/ServerTransferController.php index 6b1df3b9d6..7e3ac810f4 100644 --- a/app/Http/Controllers/Api/Remote/Servers/ServerTransferController.php +++ b/app/Http/Controllers/Api/Remote/Servers/ServerTransferController.php @@ -128,8 +128,6 @@ public function failure(string $uuid) /** * The daemon notifies us about a transfer success. * - * @return \Illuminate\Http\JsonResponse - * * @throws \Throwable */ public function success(string $uuid): JsonResponse diff --git a/app/Http/Controllers/Auth/AbstractLoginController.php b/app/Http/Controllers/Auth/AbstractLoginController.php index e50a8050f0..7c4b5c3942 100644 --- a/app/Http/Controllers/Auth/AbstractLoginController.php +++ b/app/Http/Controllers/Auth/AbstractLoginController.php @@ -92,9 +92,6 @@ protected function sendLoginResponse(User $user, Request $request): JsonResponse /** * Determine if the user is logging in using an email or username,. - * - * @param string|null $input - * @return string */ protected function getField(string $input = null): string { diff --git a/app/Http/Controllers/Auth/LoginCheckpointController.php b/app/Http/Controllers/Auth/LoginCheckpointController.php index fdb5b7e138..f554433cf5 100644 --- a/app/Http/Controllers/Auth/LoginCheckpointController.php +++ b/app/Http/Controllers/Auth/LoginCheckpointController.php @@ -2,8 +2,8 @@ namespace Pterodactyl\Http\Controllers\Auth; -use Carbon\CarbonInterface; use Carbon\CarbonImmutable; +use Carbon\CarbonInterface; use Pterodactyl\Models\User; use Illuminate\Http\JsonResponse; use PragmaRX\Google2FA\Google2FA; @@ -110,9 +110,6 @@ protected function isValidRecoveryToken(User $user, string $value) * Determines if the data provided from the session is valid or not. This * will return false if the data is invalid, or if more time has passed than * was configured when the session was written. - * - * @param array $data - * @return bool */ protected function hasValidSessionData(array $data): bool { diff --git a/app/Http/Middleware/VerifyCsrfToken.php b/app/Http/Middleware/VerifyCsrfToken.php index de10dc1a8e..6471814c68 100644 --- a/app/Http/Middleware/VerifyCsrfToken.php +++ b/app/Http/Middleware/VerifyCsrfToken.php @@ -28,7 +28,7 @@ class VerifyCsrfToken extends BaseVerifier * to using Sanctum for the API endpoints, which handles that for us automatically. * * @param \Illuminate\Http\Request $request - * @param \Closure $next + * * @return mixed * * @throws \Illuminate\Session\TokenMismatchException diff --git a/app/Services/Servers/BuildModificationService.php b/app/Services/Servers/BuildModificationService.php index 66eb52235a..224abc12fc 100644 --- a/app/Services/Servers/BuildModificationService.php +++ b/app/Services/Servers/BuildModificationService.php @@ -33,8 +33,6 @@ class BuildModificationService * BuildModificationService constructor. * * @param \Pterodactyl\Services\Servers\ServerConfigurationStructureService $structureService - * @param \Illuminate\Database\ConnectionInterface $connection - * @param \Pterodactyl\Repositories\Wings\DaemonServerRepository $daemonServerRepository */ public function __construct( ServerConfigurationStructureService $structureService, @@ -57,7 +55,7 @@ public function __construct( public function handle(Server $server, array $data) { /** @var \Pterodactyl\Models\Server $server */ - $server = $this->connection->transaction(function() use ($server, $data) { + $server = $this->connection->transaction(function () use ($server, $data) { $this->processAllocations($server, $data); if (isset($data['allocation_id']) && $data['allocation_id'] != $server->allocation_id) { diff --git a/composer.json b/composer.json index 1d6536e84a..75168ed089 100644 --- a/composer.json +++ b/composer.json @@ -47,7 +47,7 @@ "barryvdh/laravel-ide-helper": "^2.12", "facade/ignition": "^2.17", "fakerphp/faker": "^1.19", - "friendsofphp/php-cs-fixer": "^2.19", + "friendsofphp/php-cs-fixer": "^3.8", "laravel/dusk": "^6.23", "mockery/mockery": "^1.5", "nunomaduro/collision": "^5.11", @@ -70,7 +70,8 @@ } }, "scripts": { - "php-cs-fixer": "php-cs-fixer fix --diff --diff-format=udiff --config=./.php_cs.dist", + "cs:fix": "php-cs-fixer fix", + "cs:check": "php-cs-fixer fix --dry-run --diff --verbose", "post-root-package-install": [ "@php -r \"file_exists('.env') || copy('.env.example', '.env');\"" ], @@ -84,6 +85,9 @@ }, "prefer-stable": true, "config": { + "platform": { + "php": "7.4.0" + }, "preferred-install": "dist", "sort-packages": true, "optimize-autoloader": false diff --git a/composer.lock b/composer.lock index 4685158ce3..05b2bb1d7f 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "accb145afef8466a6ff78639f2b7b097", + "content-hash": "257d04481ad578251164aa3124c2ee2f", "packages": [ { "name": "aws/aws-crt-php", @@ -1916,31 +1916,31 @@ }, { "name": "lcobucci/clock", - "version": "2.2.0", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/lcobucci/clock.git", - "reference": "fb533e093fd61321bfcbac08b131ce805fe183d3" + "reference": "353d83fe2e6ae95745b16b3d911813df6a05bfb3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/lcobucci/clock/zipball/fb533e093fd61321bfcbac08b131ce805fe183d3", - "reference": "fb533e093fd61321bfcbac08b131ce805fe183d3", + "url": "https://api.github.com/repos/lcobucci/clock/zipball/353d83fe2e6ae95745b16b3d911813df6a05bfb3", + "reference": "353d83fe2e6ae95745b16b3d911813df6a05bfb3", "shasum": "" }, "require": { - "php": "^8.0", - "stella-maris/clock": "^0.1.4" + "php": "^7.4 || ^8.0" }, "require-dev": { - "infection/infection": "^0.26", - "lcobucci/coding-standard": "^8.0", - "phpstan/extension-installer": "^1.1", + "infection/infection": "^0.17", + "lcobucci/coding-standard": "^6.0", + "phpstan/extension-installer": "^1.0", "phpstan/phpstan": "^0.12", "phpstan/phpstan-deprecation-rules": "^0.12", "phpstan/phpstan-phpunit": "^0.12", "phpstan/phpstan-strict-rules": "^0.12", - "phpunit/phpunit": "^9.5" + "phpunit/php-code-coverage": "9.1.4", + "phpunit/phpunit": "9.3.7" }, "type": "library", "autoload": { @@ -1961,7 +1961,7 @@ "description": "Yet another clock abstraction", "support": { "issues": "https://github.com/lcobucci/clock/issues", - "source": "https://github.com/lcobucci/clock/tree/2.2.0" + "source": "https://github.com/lcobucci/clock/tree/2.0.x" }, "funding": [ { @@ -1973,7 +1973,7 @@ "type": "patreon" } ], - "time": "2022-04-19T19:34:17+00:00" + "time": "2020-08-27T18:56:02+00:00" }, { "name": "lcobucci/jwt", @@ -3829,30 +3829,30 @@ }, { "name": "psr/log", - "version": "2.0.0", + "version": "1.1.4", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "ef29f6d262798707a9edd554e2b82517ef3a9376" + "reference": "d49695b909c3b7628b6289db5479a1c204601f11" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/ef29f6d262798707a9edd554e2b82517ef3a9376", - "reference": "ef29f6d262798707a9edd554e2b82517ef3a9376", + "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11", "shasum": "" }, "require": { - "php": ">=8.0.0" + "php": ">=5.3.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "1.1.x-dev" } }, "autoload": { "psr-4": { - "Psr\\Log\\": "src" + "Psr\\Log\\": "Psr/Log/" } }, "notification-url": "https://packagist.org/downloads/", @@ -3873,9 +3873,9 @@ "psr-3" ], "support": { - "source": "https://github.com/php-fig/log/tree/2.0.0" + "source": "https://github.com/php-fig/log/tree/1.1.4" }, - "time": "2021-07-14T16:41:46+00:00" + "time": "2021-05-03T11:20:27+00:00" }, { "name": "psr/simple-cache", @@ -4131,24 +4131,25 @@ }, { "name": "ramsey/uuid", - "version": "4.3.1", + "version": "4.2.3", "source": { "type": "git", "url": "https://github.com/ramsey/uuid.git", - "reference": "8505afd4fea63b81a85d3b7b53ac3cb8dc347c28" + "reference": "fc9bb7fb5388691fd7373cd44dcb4d63bbcf24df" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/8505afd4fea63b81a85d3b7b53ac3cb8dc347c28", - "reference": "8505afd4fea63b81a85d3b7b53ac3cb8dc347c28", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/fc9bb7fb5388691fd7373cd44dcb4d63bbcf24df", + "reference": "fc9bb7fb5388691fd7373cd44dcb4d63bbcf24df", "shasum": "" }, "require": { "brick/math": "^0.8 || ^0.9", - "ext-ctype": "*", "ext-json": "*", - "php": "^8.0", - "ramsey/collection": "^1.0" + "php": "^7.2 || ^8.0", + "ramsey/collection": "^1.0", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-php80": "^1.14" }, "replace": { "rhumsaa/uuid": "self.version" @@ -4185,6 +4186,9 @@ }, "type": "library", "extra": { + "branch-alias": { + "dev-main": "4.x-dev" + }, "captainhook": { "force-install": true } @@ -4209,7 +4213,7 @@ ], "support": { "issues": "https://github.com/ramsey/uuid/issues", - "source": "https://github.com/ramsey/uuid/tree/4.3.1" + "source": "https://github.com/ramsey/uuid/tree/4.2.3" }, "funding": [ { @@ -4221,7 +4225,7 @@ "type": "tidelift" } ], - "time": "2022-03-27T21:42:02+00:00" + "time": "2021-09-25T23:10:38+00:00" }, { "name": "s1lentium/iptools", @@ -4542,53 +4546,6 @@ ], "time": "2021-08-19T18:23:06+00:00" }, - { - "name": "stella-maris/clock", - "version": "0.1.4", - "source": { - "type": "git", - "url": "https://gitlab.com/stella-maris/clock.git", - "reference": "8a0a967896df4c63417385dc69328a0aec84d9cf" - }, - "dist": { - "type": "zip", - "url": "https://gitlab.com/api/v4/projects/stella-maris%2Fclock/repository/archive.zip?sha=8a0a967896df4c63417385dc69328a0aec84d9cf", - "reference": "8a0a967896df4c63417385dc69328a0aec84d9cf", - "shasum": "" - }, - "require": { - "php": "^7.0|^8.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "StellaMaris\\Clock\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Andreas Heigl", - "role": "Maintainer" - } - ], - "description": "A pre-release of the proposed PSR-20 Clock-Interface", - "homepage": "https://gitlab.com/stella-maris/clock", - "keywords": [ - "clock", - "datetime", - "point in time", - "psr20" - ], - "support": { - "issues": "https://gitlab.com/stella-maris/clock/-/issues", - "source": "https://gitlab.com/stella-maris/clock/-/tree/0.1.4" - }, - "time": "2022-04-17T14:12:26+00:00" - }, { "name": "swiftmailer/swiftmailer", "version": "v6.3.0", @@ -4766,20 +4723,21 @@ }, { "name": "symfony/css-selector", - "version": "v6.0.3", + "version": "v5.4.3", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "1955d595c12c111629cc814d3f2a2ff13580508a" + "reference": "b0a190285cd95cb019237851205b8140ef6e368e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/1955d595c12c111629cc814d3f2a2ff13580508a", - "reference": "1955d595c12c111629cc814d3f2a2ff13580508a", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/b0a190285cd95cb019237851205b8140ef6e368e", + "reference": "b0a190285cd95cb019237851205b8140ef6e368e", "shasum": "" }, "require": { - "php": ">=8.0.2" + "php": ">=7.2.5", + "symfony/polyfill-php80": "^1.16" }, "type": "library", "autoload": { @@ -4811,7 +4769,7 @@ "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/css-selector/tree/v6.0.3" + "source": "https://github.com/symfony/css-selector/tree/v5.4.3" }, "funding": [ { @@ -4827,29 +4785,29 @@ "type": "tidelift" } ], - "time": "2022-01-02T09:55:41+00:00" + "time": "2022-01-02T09:53:40+00:00" }, { "name": "symfony/deprecation-contracts", - "version": "v3.0.1", + "version": "v2.5.1", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "26954b3d62a6c5fd0ea8a2a00c0353a14978d05c" + "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/26954b3d62a6c5fd0ea8a2a00c0353a14978d05c", - "reference": "26954b3d62a6c5fd0ea8a2a00c0353a14978d05c", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/e8b495ea28c1d97b5e0c121748d6f9b53d075c66", + "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66", "shasum": "" }, "require": { - "php": ">=8.0.2" + "php": ">=7.1" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "3.0-dev" + "dev-main": "2.5-dev" }, "thanks": { "name": "symfony/contracts", @@ -4878,7 +4836,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.0.1" + "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.1" }, "funding": [ { @@ -4894,7 +4852,7 @@ "type": "tidelift" } ], - "time": "2022-01-02T09:55:41+00:00" + "time": "2022-01-02T09:53:40+00:00" }, { "name": "symfony/error-handler", @@ -5054,20 +5012,20 @@ }, { "name": "symfony/event-dispatcher-contracts", - "version": "v3.0.1", + "version": "v2.5.1", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "7bc61cc2db649b4637d331240c5346dcc7708051" + "reference": "f98b54df6ad059855739db6fcbc2d36995283fe1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/7bc61cc2db649b4637d331240c5346dcc7708051", - "reference": "7bc61cc2db649b4637d331240c5346dcc7708051", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/f98b54df6ad059855739db6fcbc2d36995283fe1", + "reference": "f98b54df6ad059855739db6fcbc2d36995283fe1", "shasum": "" }, "require": { - "php": ">=8.0.2", + "php": ">=7.2.5", "psr/event-dispatcher": "^1" }, "suggest": { @@ -5076,7 +5034,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "3.0-dev" + "dev-main": "2.5-dev" }, "thanks": { "name": "symfony/contracts", @@ -5113,7 +5071,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.0.1" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v2.5.1" }, "funding": [ { @@ -5129,7 +5087,7 @@ "type": "tidelift" } ], - "time": "2022-01-02T09:55:41+00:00" + "time": "2022-01-02T09:53:40+00:00" }, { "name": "symfony/finder", @@ -6584,33 +6542,34 @@ }, { "name": "symfony/string", - "version": "v6.0.8", + "version": "v5.4.8", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "ac0aa5c2282e0de624c175b68d13f2c8f2e2649d" + "reference": "3c061a76bff6d6ea427d85e12ad1bb8ed8cd43e8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/ac0aa5c2282e0de624c175b68d13f2c8f2e2649d", - "reference": "ac0aa5c2282e0de624c175b68d13f2c8f2e2649d", + "url": "https://api.github.com/repos/symfony/string/zipball/3c061a76bff6d6ea427d85e12ad1bb8ed8cd43e8", + "reference": "3c061a76bff6d6ea427d85e12ad1bb8ed8cd43e8", "shasum": "" }, "require": { - "php": ">=8.0.2", + "php": ">=7.2.5", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-intl-grapheme": "~1.0", "symfony/polyfill-intl-normalizer": "~1.0", - "symfony/polyfill-mbstring": "~1.0" + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php80": "~1.15" }, "conflict": { - "symfony/translation-contracts": "<2.0" + "symfony/translation-contracts": ">=3.0" }, "require-dev": { - "symfony/error-handler": "^5.4|^6.0", - "symfony/http-client": "^5.4|^6.0", - "symfony/translation-contracts": "^2.0|^3.0", - "symfony/var-exporter": "^5.4|^6.0" + "symfony/error-handler": "^4.4|^5.0|^6.0", + "symfony/http-client": "^4.4|^5.0|^6.0", + "symfony/translation-contracts": "^1.1|^2", + "symfony/var-exporter": "^4.4|^5.0|^6.0" }, "type": "library", "autoload": { @@ -6649,7 +6608,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v6.0.8" + "source": "https://github.com/symfony/string/tree/v5.4.8" }, "funding": [ { @@ -6665,7 +6624,7 @@ "type": "tidelift" } ], - "time": "2022-04-22T08:18:02+00:00" + "time": "2022-04-19T10:40:37+00:00" }, { "name": "symfony/translation", @@ -7654,25 +7613,27 @@ }, { "name": "composer/xdebug-handler", - "version": "2.0.2", + "version": "3.0.3", "source": { "type": "git", "url": "https://github.com/composer/xdebug-handler.git", - "reference": "84674dd3a7575ba617f5a76d7e9e29a7d3891339" + "reference": "ced299686f41dce890debac69273b47ffe98a40c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/84674dd3a7575ba617f5a76d7e9e29a7d3891339", - "reference": "84674dd3a7575ba617f5a76d7e9e29a7d3891339", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/ced299686f41dce890debac69273b47ffe98a40c", + "reference": "ced299686f41dce890debac69273b47ffe98a40c", "shasum": "" }, "require": { - "php": "^5.3.2 || ^7.0 || ^8.0", + "composer/pcre": "^1 || ^2 || ^3", + "php": "^7.2.5 || ^8.0", "psr/log": "^1 || ^2 || ^3" }, "require-dev": { - "phpstan/phpstan": "^0.12.55", - "symfony/phpunit-bridge": "^4.2 || ^5" + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-strict-rules": "^1.1", + "symfony/phpunit-bridge": "^6.0" }, "type": "library", "autoload": { @@ -7698,7 +7659,7 @@ "support": { "irc": "irc://irc.freenode.org/composer", "issues": "https://github.com/composer/xdebug-handler/issues", - "source": "https://github.com/composer/xdebug-handler/tree/2.0.2" + "source": "https://github.com/composer/xdebug-handler/tree/3.0.3" }, "funding": [ { @@ -7714,7 +7675,7 @@ "type": "tidelift" } ], - "time": "2021-07-31T17:03:58+00:00" + "time": "2022-02-25T21:32:43+00:00" }, { "name": "doctrine/annotations", @@ -8194,85 +8155,65 @@ }, { "name": "friendsofphp/php-cs-fixer", - "version": "v2.19.3", + "version": "v3.8.0", "source": { "type": "git", "url": "https://github.com/FriendsOfPHP/PHP-CS-Fixer.git", - "reference": "75ac86f33fab4714ea5a39a396784d83ae3b5ed8" + "reference": "cbad1115aac4b5c3c5540e7210d3c9fba2f81fa3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/75ac86f33fab4714ea5a39a396784d83ae3b5ed8", - "reference": "75ac86f33fab4714ea5a39a396784d83ae3b5ed8", + "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/cbad1115aac4b5c3c5540e7210d3c9fba2f81fa3", + "reference": "cbad1115aac4b5c3c5540e7210d3c9fba2f81fa3", "shasum": "" }, "require": { - "composer/semver": "^1.4 || ^2.0 || ^3.0", - "composer/xdebug-handler": "^1.2 || ^2.0", - "doctrine/annotations": "^1.2", + "composer/semver": "^3.2", + "composer/xdebug-handler": "^3.0.3", + "doctrine/annotations": "^1.13", "ext-json": "*", "ext-tokenizer": "*", - "php": "^5.6 || ^7.0 || ^8.0", - "php-cs-fixer/diff": "^1.3", - "symfony/console": "^3.4.43 || ^4.1.6 || ^5.0", - "symfony/event-dispatcher": "^3.0 || ^4.0 || ^5.0", - "symfony/filesystem": "^3.0 || ^4.0 || ^5.0", - "symfony/finder": "^3.0 || ^4.0 || ^5.0", - "symfony/options-resolver": "^3.0 || ^4.0 || ^5.0", - "symfony/polyfill-php70": "^1.0", - "symfony/polyfill-php72": "^1.4", - "symfony/process": "^3.0 || ^4.0 || ^5.0", - "symfony/stopwatch": "^3.0 || ^4.0 || ^5.0" + "php": "^7.4 || ^8.0", + "php-cs-fixer/diff": "^2.0", + "symfony/console": "^5.4 || ^6.0", + "symfony/event-dispatcher": "^5.4 || ^6.0", + "symfony/filesystem": "^5.4 || ^6.0", + "symfony/finder": "^5.4 || ^6.0", + "symfony/options-resolver": "^5.4 || ^6.0", + "symfony/polyfill-mbstring": "^1.23", + "symfony/polyfill-php80": "^1.25", + "symfony/polyfill-php81": "^1.25", + "symfony/process": "^5.4 || ^6.0", + "symfony/stopwatch": "^5.4 || ^6.0" }, "require-dev": { - "justinrainbow/json-schema": "^5.0", - "keradus/cli-executor": "^1.4", - "mikey179/vfsstream": "^1.6", - "php-coveralls/php-coveralls": "^2.4.2", - "php-cs-fixer/accessible-object": "^1.0", + "justinrainbow/json-schema": "^5.2", + "keradus/cli-executor": "^1.5", + "mikey179/vfsstream": "^1.6.10", + "php-coveralls/php-coveralls": "^2.5.2", + "php-cs-fixer/accessible-object": "^1.1", "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.2", "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.2.1", - "phpspec/prophecy-phpunit": "^1.1 || ^2.0", - "phpunit/phpunit": "^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.13 || ^9.5", + "phpspec/prophecy": "^1.15", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.5", "phpunitgoodpractices/polyfill": "^1.5", "phpunitgoodpractices/traits": "^1.9.1", - "sanmai/phpunit-legacy-adapter": "^6.4 || ^8.2.1", - "symfony/phpunit-bridge": "^5.2.1", - "symfony/yaml": "^3.0 || ^4.0 || ^5.0" + "symfony/phpunit-bridge": "^6.0", + "symfony/yaml": "^5.4 || ^6.0" }, "suggest": { "ext-dom": "For handling output formats in XML", - "ext-mbstring": "For handling non-UTF8 characters.", - "php-cs-fixer/phpunit-constraint-isidenticalstring": "For IsIdenticalString constraint.", - "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "For XmlMatchesXsd constraint.", - "symfony/polyfill-mbstring": "When enabling `ext-mbstring` is not possible." + "ext-mbstring": "For handling non-UTF8 characters." }, "bin": [ "php-cs-fixer" ], "type": "application", - "extra": { - "branch-alias": { - "dev-master": "2.19-dev" - } - }, "autoload": { "psr-4": { "PhpCsFixer\\": "src/" - }, - "classmap": [ - "tests/Test/AbstractFixerTestCase.php", - "tests/Test/AbstractIntegrationCaseFactory.php", - "tests/Test/AbstractIntegrationTestCase.php", - "tests/Test/Assert/AssertTokensTrait.php", - "tests/Test/IntegrationCase.php", - "tests/Test/IntegrationCaseFactory.php", - "tests/Test/IntegrationCaseFactoryInterface.php", - "tests/Test/InternalIntegrationCaseFactory.php", - "tests/Test/IsIdenticalConstraint.php", - "tests/Test/TokensWithObservedTransformers.php", - "tests/TestCase.php" - ] + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -8291,7 +8232,7 @@ "description": "A tool to automatically fix PHP code style", "support": { "issues": "https://github.com/FriendsOfPHP/PHP-CS-Fixer/issues", - "source": "https://github.com/FriendsOfPHP/PHP-CS-Fixer/tree/v2.19.3" + "source": "https://github.com/FriendsOfPHP/PHP-CS-Fixer/tree/v3.8.0" }, "funding": [ { @@ -8299,7 +8240,7 @@ "type": "github" } ], - "time": "2021-11-15T17:17:55+00:00" + "time": "2022-03-18T17:20:59+00:00" }, { "name": "hamcrest/hamcrest-php", @@ -8822,16 +8763,16 @@ }, { "name": "php-cs-fixer/diff", - "version": "v1.3.1", + "version": "v2.0.2", "source": { "type": "git", "url": "https://github.com/PHP-CS-Fixer/diff.git", - "reference": "dbd31aeb251639ac0b9e7e29405c1441907f5759" + "reference": "29dc0d507e838c4580d018bd8b5cb412474f7ec3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/diff/zipball/dbd31aeb251639ac0b9e7e29405c1441907f5759", - "reference": "dbd31aeb251639ac0b9e7e29405c1441907f5759", + "url": "https://api.github.com/repos/PHP-CS-Fixer/diff/zipball/29dc0d507e838c4580d018bd8b5cb412474f7ec3", + "reference": "29dc0d507e838c4580d018bd8b5cb412474f7ec3", "shasum": "" }, "require": { @@ -8859,21 +8800,18 @@ { "name": "Kore Nordmann", "email": "mail@kore-nordmann.de" - }, - { - "name": "SpacePossum" } ], - "description": "sebastian/diff v2 backport support for PHP5.6", + "description": "sebastian/diff v3 backport support for PHP 5.6+", "homepage": "https://github.com/PHP-CS-Fixer", "keywords": [ "diff" ], "support": { "issues": "https://github.com/PHP-CS-Fixer/diff/issues", - "source": "https://github.com/PHP-CS-Fixer/diff/tree/v1.3.1" + "source": "https://github.com/PHP-CS-Fixer/diff/tree/v2.0.2" }, - "time": "2020-10-14T08:39:05+00:00" + "time": "2020-10-14T08:32:19+00:00" }, { "name": "php-mock/php-mock", @@ -10943,74 +10881,6 @@ ], "time": "2022-01-02T09:53:40+00:00" }, - { - "name": "symfony/polyfill-php70", - "version": "v1.20.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php70.git", - "reference": "5f03a781d984aae42cebd18e7912fa80f02ee644" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/5f03a781d984aae42cebd18e7912fa80f02ee644", - "reference": "5f03a781d984aae42cebd18e7912fa80f02ee644", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "type": "metapackage", - "extra": { - "branch-alias": { - "dev-main": "1.20-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 7.0+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php70/tree/v1.20.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2020-10-23T14:02:19+00:00" - }, { "name": "symfony/stopwatch", "version": "v5.4.5", @@ -11138,5 +11008,8 @@ "ext-zip": "*" }, "platform-dev": [], - "plugin-api-version": "2.3.0" + "platform-overrides": { + "php": "7.4.0" + }, + "plugin-api-version": "2.2.0" } diff --git a/config/debugbar.php b/config/debugbar.php index f1a1fd7537..cdbede09c8 100644 --- a/config/debugbar.php +++ b/config/debugbar.php @@ -130,7 +130,7 @@ 'full_log' => false, ], 'views' => [ - 'data' => false, //Note: Can slow down the application, because the data can be quite large.. + 'data' => false, // Note: Can slow down the application, because the data can be quite large.. ], 'route' => [ 'label' => true, // show complete route on bar diff --git a/tests/Integration/Api/Application/Location/LocationControllerTest.php b/tests/Integration/Api/Application/Location/LocationControllerTest.php index 939dd8622c..106df9f924 100644 --- a/tests/Integration/Api/Application/Location/LocationControllerTest.php +++ b/tests/Integration/Api/Application/Location/LocationControllerTest.php @@ -5,9 +5,9 @@ use Pterodactyl\Models\Node; use Illuminate\Http\Response; use Pterodactyl\Models\Location; -use Pterodactyl\Transformers\Api\Application\LocationTransformer; use Pterodactyl\Transformers\Api\Application\NodeTransformer; use Pterodactyl\Transformers\Api\Application\ServerTransformer; +use Pterodactyl\Transformers\Api\Application\LocationTransformer; use Pterodactyl\Tests\Integration\Api\Application\ApplicationApiIntegrationTestCase; class LocationControllerTest extends ApplicationApiIntegrationTestCase @@ -128,13 +128,13 @@ public function testUpdateLocation() $response = $this->patchJson('/api/application/locations/' . $location->id, [ 'short' => 'new inhouse', - 'long' => 'This is my new inhouse location' + 'long' => 'This is my new inhouse location', ]); $response->assertStatus(Response::HTTP_OK); $response->assertJsonCount(2); $response->assertJsonStructure([ 'object', - 'attributes' => ['id', 'short', 'long', 'created_at', 'updated_at'] + 'attributes' => ['id', 'short', 'long', 'created_at', 'updated_at'], ]); $this->assertDatabaseHas('locations', ['short' => 'new inhouse', 'long' => 'This is my new inhouse location']); diff --git a/tests/Integration/Services/Databases/DeployServerDatabaseServiceTest.php b/tests/Integration/Services/Databases/DeployServerDatabaseServiceTest.php index 2d47195719..4d6fa5ec94 100644 --- a/tests/Integration/Services/Databases/DeployServerDatabaseServiceTest.php +++ b/tests/Integration/Services/Databases/DeployServerDatabaseServiceTest.php @@ -52,7 +52,7 @@ public function testErrorIsThrownIfDatabaseNameIsEmpty($data) $server = $this->createServerModel(); $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessageMatches('/^Expected a non-empty value\. Got: /', ); + $this->expectExceptionMessageMatches('/^Expected a non-empty value\. Got: /'); $this->getService()->handle($server, $data); } From 4252014d18c980cb819a2b8c71805b379e96346c Mon Sep 17 00:00:00 2001 From: DaneEveritt Date: Wed, 4 May 2022 19:11:42 -0400 Subject: [PATCH 004/458] Update includes definition to match updated package requirements --- app/Transformers/Api/Application/AllocationTransformer.php | 2 +- app/Transformers/Api/Application/DatabaseHostTransformer.php | 2 +- app/Transformers/Api/Application/EggTransformer.php | 2 +- app/Transformers/Api/Application/LocationTransformer.php | 2 +- app/Transformers/Api/Application/NestTransformer.php | 2 +- app/Transformers/Api/Application/NodeTransformer.php | 2 +- app/Transformers/Api/Application/ServerDatabaseTransformer.php | 2 +- app/Transformers/Api/Application/ServerTransformer.php | 2 +- app/Transformers/Api/Application/ServerVariableTransformer.php | 2 +- app/Transformers/Api/Application/SubuserTransformer.php | 2 +- app/Transformers/Api/Application/UserTransformer.php | 2 +- app/Transformers/Api/Client/DatabaseTransformer.php | 2 +- app/Transformers/Api/Client/ScheduleTransformer.php | 2 +- app/Transformers/Api/Client/ServerTransformer.php | 2 +- 14 files changed, 14 insertions(+), 14 deletions(-) diff --git a/app/Transformers/Api/Application/AllocationTransformer.php b/app/Transformers/Api/Application/AllocationTransformer.php index 1352947717..cc749e194c 100644 --- a/app/Transformers/Api/Application/AllocationTransformer.php +++ b/app/Transformers/Api/Application/AllocationTransformer.php @@ -14,7 +14,7 @@ class AllocationTransformer extends BaseTransformer * * @var array */ - protected $availableIncludes = ['node', 'server']; + protected array $availableIncludes = ['node', 'server']; /** * Return the resource name for the JSONAPI output. diff --git a/app/Transformers/Api/Application/DatabaseHostTransformer.php b/app/Transformers/Api/Application/DatabaseHostTransformer.php index be888df5cf..3855cf3722 100644 --- a/app/Transformers/Api/Application/DatabaseHostTransformer.php +++ b/app/Transformers/Api/Application/DatabaseHostTransformer.php @@ -11,7 +11,7 @@ class DatabaseHostTransformer extends BaseTransformer /** * @var array */ - protected $availableIncludes = [ + protected array $availableIncludes = [ 'databases', ]; diff --git a/app/Transformers/Api/Application/EggTransformer.php b/app/Transformers/Api/Application/EggTransformer.php index a1348a0ad4..8dccb7552b 100644 --- a/app/Transformers/Api/Application/EggTransformer.php +++ b/app/Transformers/Api/Application/EggTransformer.php @@ -15,7 +15,7 @@ class EggTransformer extends BaseTransformer * * @var array */ - protected $availableIncludes = [ + protected array $availableIncludes = [ 'nest', 'servers', 'config', diff --git a/app/Transformers/Api/Application/LocationTransformer.php b/app/Transformers/Api/Application/LocationTransformer.php index dfa8315ec8..4276677340 100644 --- a/app/Transformers/Api/Application/LocationTransformer.php +++ b/app/Transformers/Api/Application/LocationTransformer.php @@ -12,7 +12,7 @@ class LocationTransformer extends BaseTransformer * * @var array */ - protected $availableIncludes = ['nodes', 'servers']; + protected array $availableIncludes = ['nodes', 'servers']; /** * Return the resource name for the JSONAPI output. diff --git a/app/Transformers/Api/Application/NestTransformer.php b/app/Transformers/Api/Application/NestTransformer.php index 506b0276b6..29f9559ae8 100644 --- a/app/Transformers/Api/Application/NestTransformer.php +++ b/app/Transformers/Api/Application/NestTransformer.php @@ -14,7 +14,7 @@ class NestTransformer extends BaseTransformer * * @var array */ - protected $availableIncludes = [ + protected array $availableIncludes = [ 'eggs', 'servers', ]; diff --git a/app/Transformers/Api/Application/NodeTransformer.php b/app/Transformers/Api/Application/NodeTransformer.php index aaed68e9b8..fc7d9c0f06 100644 --- a/app/Transformers/Api/Application/NodeTransformer.php +++ b/app/Transformers/Api/Application/NodeTransformer.php @@ -12,7 +12,7 @@ class NodeTransformer extends BaseTransformer * * @var array */ - protected $availableIncludes = ['allocations', 'location', 'servers']; + protected array $availableIncludes = ['allocations', 'location', 'servers']; /** * Return the resource name for the JSONAPI output. diff --git a/app/Transformers/Api/Application/ServerDatabaseTransformer.php b/app/Transformers/Api/Application/ServerDatabaseTransformer.php index 95180e2d9a..58de97a63e 100644 --- a/app/Transformers/Api/Application/ServerDatabaseTransformer.php +++ b/app/Transformers/Api/Application/ServerDatabaseTransformer.php @@ -12,7 +12,7 @@ class ServerDatabaseTransformer extends BaseTransformer /** * @var array */ - protected $availableIncludes = ['password', 'host']; + protected array $availableIncludes = ['password', 'host']; /** * @var Encrypter diff --git a/app/Transformers/Api/Application/ServerTransformer.php b/app/Transformers/Api/Application/ServerTransformer.php index f516380226..b96c0aa6bd 100644 --- a/app/Transformers/Api/Application/ServerTransformer.php +++ b/app/Transformers/Api/Application/ServerTransformer.php @@ -18,7 +18,7 @@ class ServerTransformer extends BaseTransformer * * @var array */ - protected $availableIncludes = [ + protected array $availableIncludes = [ 'allocations', 'user', 'subusers', diff --git a/app/Transformers/Api/Application/ServerVariableTransformer.php b/app/Transformers/Api/Application/ServerVariableTransformer.php index aefa318e26..28d0650480 100644 --- a/app/Transformers/Api/Application/ServerVariableTransformer.php +++ b/app/Transformers/Api/Application/ServerVariableTransformer.php @@ -12,7 +12,7 @@ class ServerVariableTransformer extends BaseTransformer * * @var array */ - protected $availableIncludes = ['parent']; + protected array $availableIncludes = ['parent']; /** * Return the resource name for the JSONAPI output. diff --git a/app/Transformers/Api/Application/SubuserTransformer.php b/app/Transformers/Api/Application/SubuserTransformer.php index 08c6226666..7ce9eba3c4 100644 --- a/app/Transformers/Api/Application/SubuserTransformer.php +++ b/app/Transformers/Api/Application/SubuserTransformer.php @@ -12,7 +12,7 @@ class SubuserTransformer extends BaseTransformer * * @var array */ - protected $availableIncludes = ['user', 'server']; + protected array $availableIncludes = ['user', 'server']; /** * Return the resource name for the JSONAPI output. diff --git a/app/Transformers/Api/Application/UserTransformer.php b/app/Transformers/Api/Application/UserTransformer.php index b3b4235079..8202512fe0 100644 --- a/app/Transformers/Api/Application/UserTransformer.php +++ b/app/Transformers/Api/Application/UserTransformer.php @@ -12,7 +12,7 @@ class UserTransformer extends BaseTransformer * * @var array */ - protected $availableIncludes = ['servers']; + protected array $availableIncludes = ['servers']; /** * Return the resource name for the JSONAPI output. diff --git a/app/Transformers/Api/Client/DatabaseTransformer.php b/app/Transformers/Api/Client/DatabaseTransformer.php index 9c5bd86d4f..4857ea0272 100644 --- a/app/Transformers/Api/Client/DatabaseTransformer.php +++ b/app/Transformers/Api/Client/DatabaseTransformer.php @@ -9,7 +9,7 @@ class DatabaseTransformer extends BaseClientTransformer { - protected $availableIncludes = ['password']; + protected array $availableIncludes = ['password']; /** * @var \Illuminate\Contracts\Encryption\Encrypter diff --git a/app/Transformers/Api/Client/ScheduleTransformer.php b/app/Transformers/Api/Client/ScheduleTransformer.php index 25ef9fb583..ed718ce2b6 100644 --- a/app/Transformers/Api/Client/ScheduleTransformer.php +++ b/app/Transformers/Api/Client/ScheduleTransformer.php @@ -11,7 +11,7 @@ class ScheduleTransformer extends BaseClientTransformer /** * @var array */ - protected $availableIncludes = ['tasks']; + protected array $availableIncludes = ['tasks']; /** * @var array diff --git a/app/Transformers/Api/Client/ServerTransformer.php b/app/Transformers/Api/Client/ServerTransformer.php index c8c10a9b2b..fc393d2f8d 100644 --- a/app/Transformers/Api/Client/ServerTransformer.php +++ b/app/Transformers/Api/Client/ServerTransformer.php @@ -21,7 +21,7 @@ class ServerTransformer extends BaseClientTransformer /** * @var array */ - protected $availableIncludes = ['egg', 'subusers']; + protected array $availableIncludes = ['egg', 'subusers']; public function getResourceName(): string { From f5ad9b9e113e34f3f792ea802a7f738b2406f35f Mon Sep 17 00:00:00 2001 From: DaneEveritt Date: Wed, 4 May 2022 19:19:00 -0400 Subject: [PATCH 005/458] Replace debug bar with clockwork --- composer.json | 2 +- composer.lock | 289 ++++++++-------------------------- config/debugbar.php | 167 -------------------- resources/scripts/api/http.ts | 16 +- storage/clockwork/.gitignore | 2 + 5 files changed, 74 insertions(+), 402 deletions(-) delete mode 100644 config/debugbar.php create mode 100644 storage/clockwork/.gitignore diff --git a/composer.json b/composer.json index 75168ed089..947272d373 100644 --- a/composer.json +++ b/composer.json @@ -43,11 +43,11 @@ "webmozart/assert": "~1.10.0" }, "require-dev": { - "barryvdh/laravel-debugbar": "^3.6", "barryvdh/laravel-ide-helper": "^2.12", "facade/ignition": "^2.17", "fakerphp/faker": "^1.19", "friendsofphp/php-cs-fixer": "^3.8", + "itsgoingd/clockwork": "^5.1", "laravel/dusk": "^6.23", "mockery/mockery": "^1.5", "nunomaduro/collision": "^5.11", diff --git a/composer.lock b/composer.lock index 05b2bb1d7f..eb9d574ce8 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "257d04481ad578251164aa3124c2ee2f", + "content-hash": "d51c727b5c04b52d1d56e2e64e8355b9", "packages": [ { "name": "aws/aws-crt-php", @@ -7228,91 +7228,6 @@ } ], "packages-dev": [ - { - "name": "barryvdh/laravel-debugbar", - "version": "v3.6.7", - "source": { - "type": "git", - "url": "https://github.com/barryvdh/laravel-debugbar.git", - "reference": "b96f9820aaf1ff9afe945207883149e1c7afb298" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/b96f9820aaf1ff9afe945207883149e1c7afb298", - "reference": "b96f9820aaf1ff9afe945207883149e1c7afb298", - "shasum": "" - }, - "require": { - "illuminate/routing": "^6|^7|^8|^9", - "illuminate/session": "^6|^7|^8|^9", - "illuminate/support": "^6|^7|^8|^9", - "maximebf/debugbar": "^1.17.2", - "php": ">=7.2", - "symfony/debug": "^4.3|^5|^6", - "symfony/finder": "^4.3|^5|^6" - }, - "require-dev": { - "mockery/mockery": "^1.3.3", - "orchestra/testbench-dusk": "^4|^5|^6|^7", - "phpunit/phpunit": "^8.5|^9.0", - "squizlabs/php_codesniffer": "^3.5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.6-dev" - }, - "laravel": { - "providers": [ - "Barryvdh\\Debugbar\\ServiceProvider" - ], - "aliases": { - "Debugbar": "Barryvdh\\Debugbar\\Facades\\Debugbar" - } - } - }, - "autoload": { - "files": [ - "src/helpers.php" - ], - "psr-4": { - "Barryvdh\\Debugbar\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Barry vd. Heuvel", - "email": "barryvdh@gmail.com" - } - ], - "description": "PHP Debugbar integration for Laravel", - "keywords": [ - "debug", - "debugbar", - "laravel", - "profiler", - "webprofiler" - ], - "support": { - "issues": "https://github.com/barryvdh/laravel-debugbar/issues", - "source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.6.7" - }, - "funding": [ - { - "url": "https://fruitcake.nl", - "type": "custom" - }, - { - "url": "https://github.com/barryvdh", - "type": "github" - } - ], - "time": "2022-02-09T07:52:32+00:00" - }, { "name": "barryvdh/laravel-ide-helper", "version": "v2.12.3", @@ -8293,6 +8208,74 @@ }, "time": "2020-07-09T08:09:16+00:00" }, + { + "name": "itsgoingd/clockwork", + "version": "v5.1.6", + "source": { + "type": "git", + "url": "https://github.com/itsgoingd/clockwork.git", + "reference": "9df41432da1d8cb39c7fda383ddcc02231c83ff3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/itsgoingd/clockwork/zipball/9df41432da1d8cb39c7fda383ddcc02231c83ff3", + "reference": "9df41432da1d8cb39c7fda383ddcc02231c83ff3", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": ">=5.6" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Clockwork\\Support\\Laravel\\ClockworkServiceProvider" + ], + "aliases": { + "Clockwork": "Clockwork\\Support\\Laravel\\Facade" + } + } + }, + "autoload": { + "psr-4": { + "Clockwork\\": "Clockwork/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "itsgoingd", + "email": "itsgoingd@luzer.sk", + "homepage": "https://twitter.com/itsgoingd" + } + ], + "description": "php dev tools in your browser", + "homepage": "https://underground.works/clockwork", + "keywords": [ + "Devtools", + "debugging", + "laravel", + "logging", + "lumen", + "profiling", + "slim" + ], + "support": { + "issues": "https://github.com/itsgoingd/clockwork/issues", + "source": "https://github.com/itsgoingd/clockwork/tree/v5.1.6" + }, + "funding": [ + { + "url": "https://github.com/itsgoingd", + "type": "github" + } + ], + "time": "2022-04-12T21:35:47+00:00" + }, { "name": "laravel/dusk", "version": "v6.23.1", @@ -8366,72 +8349,6 @@ }, "time": "2022-05-02T14:01:47+00:00" }, - { - "name": "maximebf/debugbar", - "version": "v1.18.0", - "source": { - "type": "git", - "url": "https://github.com/maximebf/php-debugbar.git", - "reference": "0d44b75f3b5d6d41ae83b79c7a4bceae7fbc78b6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/0d44b75f3b5d6d41ae83b79c7a4bceae7fbc78b6", - "reference": "0d44b75f3b5d6d41ae83b79c7a4bceae7fbc78b6", - "shasum": "" - }, - "require": { - "php": "^7.1|^8", - "psr/log": "^1|^2|^3", - "symfony/var-dumper": "^2.6|^3|^4|^5|^6" - }, - "require-dev": { - "phpunit/phpunit": "^7.5.20 || ^9.4.2", - "twig/twig": "^1.38|^2.7|^3.0" - }, - "suggest": { - "kriswallsmith/assetic": "The best way to manage assets", - "monolog/monolog": "Log using Monolog", - "predis/predis": "Redis storage" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.17-dev" - } - }, - "autoload": { - "psr-4": { - "DebugBar\\": "src/DebugBar/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Maxime Bouroumeau-Fuseau", - "email": "maxime.bouroumeau@gmail.com", - "homepage": "http://maximebf.com" - }, - { - "name": "Barry vd. Heuvel", - "email": "barryvdh@gmail.com" - } - ], - "description": "Debug bar in the browser for php application", - "homepage": "https://github.com/maximebf/php-debugbar", - "keywords": [ - "debug", - "debugbar" - ], - "support": { - "issues": "https://github.com/maximebf/php-debugbar/issues", - "source": "https://github.com/maximebf/php-debugbar/tree/v1.18.0" - }, - "time": "2021-12-27T18:49:48+00:00" - }, { "name": "mockery/mockery", "version": "1.5.0", @@ -10680,74 +10597,6 @@ ], "time": "2020-09-28T06:39:44+00:00" }, - { - "name": "symfony/debug", - "version": "v4.4.41", - "source": { - "type": "git", - "url": "https://github.com/symfony/debug.git", - "reference": "6637e62480b60817b9a6984154a533e8e64c6bd5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/debug/zipball/6637e62480b60817b9a6984154a533e8e64c6bd5", - "reference": "6637e62480b60817b9a6984154a533e8e64c6bd5", - "shasum": "" - }, - "require": { - "php": ">=7.1.3", - "psr/log": "^1|^2|^3" - }, - "conflict": { - "symfony/http-kernel": "<3.4" - }, - "require-dev": { - "symfony/http-kernel": "^3.4|^4.0|^5.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Debug\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides tools to ease debugging PHP code", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/debug/tree/v4.4.41" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-04-12T15:19:55+00:00" - }, { "name": "symfony/filesystem", "version": "v5.4.7", diff --git a/config/debugbar.php b/config/debugbar.php deleted file mode 100644 index cdbede09c8..0000000000 --- a/config/debugbar.php +++ /dev/null @@ -1,167 +0,0 @@ - null, - - /* - |-------------------------------------------------------------------------- - | Storage settings - |-------------------------------------------------------------------------- - | - | DebugBar stores data for session/ajax requests. - | You can disable this, so the debugbar stores data in headers/session, - | but this can cause problems with large data collectors. - | By default, file storage (in the storage folder) is used. Redis and PDO - | can also be used. For PDO, run the package migrations first. - | - */ - 'storage' => [ - 'enabled' => true, - 'driver' => env('DEBUGBAR_DRIVER', 'file'), // redis, file, pdo - 'path' => storage_path() . '/debugbar', // For file driver - 'connection' => null, // Leave null for default connection (Redis/PDO) - ], - - /* - |-------------------------------------------------------------------------- - | Vendors - |-------------------------------------------------------------------------- - | - | Vendor files are included by default, but can be set to false. - | This can also be set to 'js' or 'css', to only include javascript or css vendor files. - | Vendor files are for css: font-awesome (including fonts) and highlight.js (css files) - | and for js: jquery and and highlight.js - | So if you want syntax highlighting, set it to true. - | jQuery is set to not conflict with existing jQuery scripts. - | - */ - - 'include_vendors' => true, - - /* - |-------------------------------------------------------------------------- - | Capture Ajax Requests - |-------------------------------------------------------------------------- - | - | The Debugbar can capture Ajax requests and display them. If you don't want this (ie. because of errors), - | you can use this option to disable sending the data through the headers. - | - */ - - 'capture_ajax' => true, - - /* - |-------------------------------------------------------------------------- - | Clockwork integration - |-------------------------------------------------------------------------- - | - | The Debugbar can emulate the Clockwork headers, so you can use the Chrome - | Extension, without the server-side code. It uses Debugbar collectors instead. - | - */ - 'clockwork' => false, - - /* - |-------------------------------------------------------------------------- - | DataCollectors - |-------------------------------------------------------------------------- - | - | Enable/disable DataCollectors - | - */ - - 'collectors' => [ - 'phpinfo' => true, // Php version - 'messages' => true, // Messages - 'time' => true, // Time Datalogger - 'memory' => true, // Memory usage - 'exceptions' => true, // Exception displayer - 'log' => true, // Logs from Monolog (merged in messages if enabled) - 'db' => true, // Show database (PDO) queries and bindings - 'views' => true, // Views with their data - 'route' => true, // Current route information - 'laravel' => false, // Laravel version and environment - 'events' => true, // All events fired - 'default_request' => false, // Regular or special Symfony request logger - 'symfony_request' => true, // Only one can be enabled.. - 'mail' => true, // Catch mail messages - 'logs' => false, // Add the latest log messages - 'files' => false, // Show the included files - 'config' => false, // Display config settings - 'auth' => false, // Display Laravel authentication status - 'gate' => false, // Display Laravel Gate checks - 'session' => true, // Display session data - ], - - /* - |-------------------------------------------------------------------------- - | Extra options - |-------------------------------------------------------------------------- - | - | Configure some DataCollectors - | - */ - - 'options' => [ - 'auth' => [ - 'show_name' => false, // Also show the users name/email in the debugbar - ], - 'db' => [ - 'with_params' => true, // Render SQL with the parameters substituted - 'timeline' => true, // Add the queries to the timeline - 'backtrace' => true, // EXPERIMENTAL: Use a backtrace to find the origin of the query in your files. - 'explain' => [ // EXPERIMENTAL: Show EXPLAIN output on queries - 'enabled' => false, - 'types' => ['SELECT', 'INSERT', 'UPDATE', 'DELETE'], // array('SELECT', 'INSERT', 'UPDATE', 'DELETE'); for MySQL 5.6.3+ - ], - 'hints' => false, // Show hints for common mistakes - ], - 'mail' => [ - 'full_log' => false, - ], - 'views' => [ - 'data' => false, // Note: Can slow down the application, because the data can be quite large.. - ], - 'route' => [ - 'label' => true, // show complete route on bar - ], - 'logs' => [ - 'file' => null, - ], - ], - - /* - |-------------------------------------------------------------------------- - | Inject Debugbar in Response - |-------------------------------------------------------------------------- - | - | Usually, the debugbar is added just before , by listening to the - | Response after the App is done. If you disable this, you have to add them - | in your template yourself. See http://phpdebugbar.com/docs/rendering.html - | - */ - - 'inject' => true, - - /* - |-------------------------------------------------------------------------- - | DebugBar route prefix - |-------------------------------------------------------------------------- - | - | Sometimes you want to set route prefix to be used by DebugBar to load - | its resources from. Usually the need comes from misconfigured web server or - | from trying to overcome bugs like this: http://trac.nginx.org/nginx/ticket/97 - | - */ - 'route_prefix' => '_debugbar', -]; diff --git a/resources/scripts/api/http.ts b/resources/scripts/api/http.ts index d9f64ede2a..9f810b648b 100644 --- a/resources/scripts/api/http.ts +++ b/resources/scripts/api/http.ts @@ -23,7 +23,7 @@ http.interceptors.request.use(req => { }); http.interceptors.request.use(req => { - if (!req.url?.endsWith('/resources') && (req.url?.indexOf('_debugbar') || -1) < 0) { + if (!req.url?.endsWith('/resources')) { store.getActions().progress.startContinuous(); } @@ -31,7 +31,7 @@ http.interceptors.request.use(req => { }); http.interceptors.response.use(resp => { - if (!resp.request?.url?.endsWith('/resources') && (resp.request?.url?.indexOf('_debugbar') || -1) < 0) { + if (!resp.request?.url?.endsWith('/resources')) { store.getActions().progress.setComplete(); } @@ -42,18 +42,6 @@ http.interceptors.response.use(resp => { throw error; }); -// If we have a phpdebugbar instance registered at this point in time go -// ahead and route the response data through to it so things show up. -// @ts-ignore -if (typeof window.phpdebugbar !== 'undefined') { - http.interceptors.response.use(response => { - // @ts-ignore - window.phpdebugbar.ajaxHandler.handle(response.request); - - return response; - }); -} - export default http; /** diff --git a/storage/clockwork/.gitignore b/storage/clockwork/.gitignore new file mode 100644 index 0000000000..d6b7ef32c8 --- /dev/null +++ b/storage/clockwork/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore From 530558b0f82bd5509b374acb9c2628a846995ac3 Mon Sep 17 00:00:00 2001 From: DaneEveritt Date: Wed, 4 May 2022 19:23:01 -0400 Subject: [PATCH 006/458] Update deprecated JSON response creation and unnecessary middleware --- .../Admin/NodeAutoDeployController.php | 2 +- .../Nodes/SystemInformationController.php | 2 +- .../Nodes/NodeConfigurationController.php | 2 +- .../Servers/DatabaseController.php | 2 +- .../Api/Client/ApiKeyController.php | 2 +- .../Servers/ServerInstallController.php | 2 +- .../Controllers/Base/LocaleController.php | 2 +- app/Http/Kernel.php | 2 +- app/Http/Middleware/TrustProxies.php | 23 ------- composer.json | 1 - composer.lock | 60 +------------------ 11 files changed, 9 insertions(+), 91 deletions(-) delete mode 100644 app/Http/Middleware/TrustProxies.php diff --git a/app/Http/Controllers/Admin/NodeAutoDeployController.php b/app/Http/Controllers/Admin/NodeAutoDeployController.php index a638a3f1d2..79305a38b8 100644 --- a/app/Http/Controllers/Admin/NodeAutoDeployController.php +++ b/app/Http/Controllers/Admin/NodeAutoDeployController.php @@ -74,7 +74,7 @@ public function __invoke(Request $request, Node $node) ], ['r_nodes' => 1]); } - return JsonResponse::create([ + return new JsonResponse([ 'node' => $node->id, 'token' => $key->identifier . $this->encrypter->decrypt($key->token), ]); diff --git a/app/Http/Controllers/Admin/Nodes/SystemInformationController.php b/app/Http/Controllers/Admin/Nodes/SystemInformationController.php index 476cea49c4..954897a885 100644 --- a/app/Http/Controllers/Admin/Nodes/SystemInformationController.php +++ b/app/Http/Controllers/Admin/Nodes/SystemInformationController.php @@ -35,7 +35,7 @@ public function __invoke(Request $request, Node $node) { $data = $this->repository->setNode($node)->getSystemInformation(); - return JsonResponse::create([ + return new JsonResponse([ 'version' => $data['version'] ?? '', 'system' => [ 'type' => Str::title($data['os'] ?? 'Unknown'), diff --git a/app/Http/Controllers/Api/Application/Nodes/NodeConfigurationController.php b/app/Http/Controllers/Api/Application/Nodes/NodeConfigurationController.php index 058f542b6e..95ae70f3b9 100644 --- a/app/Http/Controllers/Api/Application/Nodes/NodeConfigurationController.php +++ b/app/Http/Controllers/Api/Application/Nodes/NodeConfigurationController.php @@ -18,6 +18,6 @@ class NodeConfigurationController extends ApplicationApiController */ public function __invoke(GetNodeRequest $request, Node $node) { - return JsonResponse::create($node->getConfiguration()); + return new JsonResponse($node->getConfiguration()); } } diff --git a/app/Http/Controllers/Api/Application/Servers/DatabaseController.php b/app/Http/Controllers/Api/Application/Servers/DatabaseController.php index 73f96737de..fc17562306 100644 --- a/app/Http/Controllers/Api/Application/Servers/DatabaseController.php +++ b/app/Http/Controllers/Api/Application/Servers/DatabaseController.php @@ -78,7 +78,7 @@ public function resetPassword(ServerDatabaseWriteRequest $request, Server $serve { $this->databasePasswordService->handle($database); - return JsonResponse::create([], JsonResponse::HTTP_NO_CONTENT); + return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT); } /** diff --git a/app/Http/Controllers/Api/Client/ApiKeyController.php b/app/Http/Controllers/Api/Client/ApiKeyController.php index 34694d66c4..b978cc8d4c 100644 --- a/app/Http/Controllers/Api/Client/ApiKeyController.php +++ b/app/Http/Controllers/Api/Client/ApiKeyController.php @@ -102,6 +102,6 @@ public function delete(ClientApiRequest $request, string $identifier) throw new NotFoundHttpException(); } - return JsonResponse::create([], JsonResponse::HTTP_NO_CONTENT); + return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT); } } diff --git a/app/Http/Controllers/Api/Remote/Servers/ServerInstallController.php b/app/Http/Controllers/Api/Remote/Servers/ServerInstallController.php index be67ee53a9..0e24f71be3 100644 --- a/app/Http/Controllers/Api/Remote/Servers/ServerInstallController.php +++ b/app/Http/Controllers/Api/Remote/Servers/ServerInstallController.php @@ -45,7 +45,7 @@ public function index(Request $request, string $uuid) $server = $this->repository->getByUuid($uuid); $egg = $server->egg; - return JsonResponse::create([ + return new JsonResponse([ 'container_image' => $egg->copy_script_container, 'entrypoint' => $egg->copy_script_entry, 'script' => $egg->copy_script_install, diff --git a/app/Http/Controllers/Base/LocaleController.php b/app/Http/Controllers/Base/LocaleController.php index 3bb6fbfba2..b6f2da4f57 100644 --- a/app/Http/Controllers/Base/LocaleController.php +++ b/app/Http/Controllers/Base/LocaleController.php @@ -31,7 +31,7 @@ public function __invoke(Request $request, string $locale, string $namespace) { $data = $this->translator->getLoader()->load($locale, str_replace('.', '/', $namespace)); - return JsonResponse::create($data, 200, [ + return new JsonResponse($data, 200, [ 'E-Tag' => md5(json_encode($data)), ]); } diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 3fac902c1b..cf7b4a8d35 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -2,11 +2,11 @@ namespace Pterodactyl\Http; +use Illuminate\Http\Middleware\TrustProxies; use Pterodactyl\Models\ApiKey; use Illuminate\Auth\Middleware\Authorize; use Illuminate\Auth\Middleware\Authenticate; use Pterodactyl\Http\Middleware\TrimStrings; -use Pterodactyl\Http\Middleware\TrustProxies; use Illuminate\Session\Middleware\StartSession; use Pterodactyl\Http\Middleware\EncryptCookies; use Pterodactyl\Http\Middleware\Api\IsValidJson; diff --git a/app/Http/Middleware/TrustProxies.php b/app/Http/Middleware/TrustProxies.php deleted file mode 100644 index 3dff63eeb8..0000000000 --- a/app/Http/Middleware/TrustProxies.php +++ /dev/null @@ -1,23 +0,0 @@ -=5.4.0" - }, - "require-dev": { - "illuminate/http": "^5.0|^6.0|^7.0|^8.0|^9.0", - "mockery/mockery": "^1.0", - "phpunit/phpunit": "^6.0" - }, - "type": "library", - "extra": { - "laravel": { - "providers": [ - "Fideloper\\Proxy\\TrustedProxyServiceProvider" - ] - } - }, - "autoload": { - "psr-4": { - "Fideloper\\Proxy\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Chris Fidao", - "email": "fideloper@gmail.com" - } - ], - "description": "Set trusted proxies for Laravel", - "keywords": [ - "load balancing", - "proxy", - "trusted proxy" - ], - "support": { - "issues": "https://github.com/fideloper/TrustedProxy/issues", - "source": "https://github.com/fideloper/TrustedProxy/tree/4.4.1" - }, - "time": "2020-10-22T13:48:01+00:00" - }, { "name": "graham-campbell/result-type", "version": "v1.0.4", From 8c63eebf135aafa60c2ff13fc7d414911731a1d7 Mon Sep 17 00:00:00 2001 From: DaneEveritt Date: Wed, 4 May 2022 19:35:10 -0400 Subject: [PATCH 007/458] Fix fractal errors --- .../Serializers/PterodactylSerializer.php | 23 ++++--------------- .../Api/Client/ScheduleTransformer.php | 2 +- .../Api/Client/ServerTransformer.php | 2 +- 3 files changed, 6 insertions(+), 21 deletions(-) diff --git a/app/Extensions/League/Fractal/Serializers/PterodactylSerializer.php b/app/Extensions/League/Fractal/Serializers/PterodactylSerializer.php index 2481cf96e4..5b53a5ad70 100644 --- a/app/Extensions/League/Fractal/Serializers/PterodactylSerializer.php +++ b/app/Extensions/League/Fractal/Serializers/PterodactylSerializer.php @@ -8,12 +8,8 @@ class PterodactylSerializer extends ArraySerializer { /** * Serialize an item. - * - * @param string $resourceKey - * - * @return array */ - public function item($resourceKey, array $data) + public function item(?string $resourceKey, array $data): array { return [ 'object' => $resourceKey, @@ -23,12 +19,8 @@ public function item($resourceKey, array $data) /** * Serialize a collection. - * - * @param string $resourceKey - * - * @return array */ - public function collection($resourceKey, array $data) + public function collection(?string $resourceKey, array $data): array { $response = []; foreach ($data as $datum) { @@ -43,10 +35,8 @@ public function collection($resourceKey, array $data) /** * Serialize a null resource. - * - * @return array */ - public function null() + public function null(): ?array { return [ 'object' => 'null_resource', @@ -56,13 +46,8 @@ public function null() /** * Merge the included resources with the parent resource being serialized. - * - * @param array $transformedData - * @param array $includedData - * - * @return array */ - public function mergeIncludes($transformedData, $includedData) + public function mergeIncludes(array $transformedData, array $includedData): array { foreach ($includedData as $key => $datum) { $transformedData['relationships'][$key] = $datum; diff --git a/app/Transformers/Api/Client/ScheduleTransformer.php b/app/Transformers/Api/Client/ScheduleTransformer.php index ed718ce2b6..3f42234b2a 100644 --- a/app/Transformers/Api/Client/ScheduleTransformer.php +++ b/app/Transformers/Api/Client/ScheduleTransformer.php @@ -16,7 +16,7 @@ class ScheduleTransformer extends BaseClientTransformer /** * @var array */ - protected $defaultIncludes = ['tasks']; + protected array $defaultIncludes = ['tasks']; /** * {@inheritdoc} diff --git a/app/Transformers/Api/Client/ServerTransformer.php b/app/Transformers/Api/Client/ServerTransformer.php index fc393d2f8d..1e4412b915 100644 --- a/app/Transformers/Api/Client/ServerTransformer.php +++ b/app/Transformers/Api/Client/ServerTransformer.php @@ -16,7 +16,7 @@ class ServerTransformer extends BaseClientTransformer /** * @var string[] */ - protected $defaultIncludes = ['allocations', 'variables']; + protected array $defaultIncludes = ['allocations', 'variables']; /** * @var array From 66da520e11f5cbf91fc860cffaac7e99ad541b4e Mon Sep 17 00:00:00 2001 From: DaneEveritt Date: Wed, 4 May 2022 20:41:04 -0400 Subject: [PATCH 008/458] Simplify testing process for integration tests, don't require second connection --- .env.ci | 8 ++++---- .github/workflows/tests.yml | 9 ++++----- bootstrap/tests.php | 9 +++++---- config/database.php | 23 ----------------------- phpunit.xml | 28 +++++++++------------------- 5 files changed, 22 insertions(+), 55 deletions(-) diff --git a/.env.ci b/.env.ci index e99f46691b..8d08487342 100644 --- a/.env.ci +++ b/.env.ci @@ -5,10 +5,10 @@ APP_THEME=pterodactyl APP_TIMEZONE=America/Los_Angeles APP_URL=http://localhost/ -TESTING_DB_HOST=127.0.0.1 -TESTING_DB_DATABASE=panel_test -TESTING_DB_USERNAME=root -TESTING_DB_PASSWORD= +DB_HOST=127.0.0.1 +DB_DATABASE=panel_test +DB_USERNAME=root +DB_PASSWORD= CACHE_DRIVER=array SESSION_DRIVER=array diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a59f99a476..e72212b114 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -12,7 +12,7 @@ jobs: strategy: fail-fast: false matrix: - php: [ 7.4, 8.0 ] + php: [ 7.4, 8.0, 8.1 ] database: [ 'mariadb:10.2', 'mysql:8' ] services: database: @@ -50,10 +50,9 @@ jobs: run: vendor/bin/phpunit --bootstrap vendor/autoload.php tests/Unit if: ${{ always() }} env: - DB_CONNECTION: testing - TESTING_DB_HOST: UNIT_NO_DB + DB_HOST: UNIT_NO_DB - name: execute integration tests run: vendor/bin/phpunit tests/Integration env: - TESTING_DB_PORT: ${{ job.services.database.ports[3306] }} - TESTING_DB_USERNAME: root + DB_PORT: ${{ job.services.database.ports[3306] }} + DB_USERNAME: root diff --git a/bootstrap/tests.php b/bootstrap/tests.php index 3503d2ac19..35479a0cd6 100644 --- a/bootstrap/tests.php +++ b/bootstrap/tests.php @@ -22,9 +22,10 @@ $output = new ConsoleOutput(); -if (config('database.default') !== 'testing') { +$prefix = 'database.connections.' . config('database.default'); +if (config("$prefix.database") !== 'panel_test') { $output->writeln(PHP_EOL . 'Cannot run test process against non-testing database.'); - $output->writeln(PHP_EOL . 'Environment is currently pointed at: "' . config('database.default') . '".'); + $output->writeln(PHP_EOL . 'Environment is currently pointed at: "' . config("$prefix.database") . '".'); exit(1); } @@ -34,10 +35,10 @@ */ if (!env('SKIP_MIGRATIONS')) { $output->writeln(PHP_EOL . 'Refreshing database for Integration tests...'); - $kernel->call('migrate:fresh', ['--database' => 'testing']); + $kernel->call('migrate:fresh'); $output->writeln('Seeding database for Integration tests...' . PHP_EOL); - $kernel->call('db:seed', ['--database' => 'testing']); + $kernel->call('db:seed'); } else { $output->writeln(PHP_EOL . 'Skipping database migrations...' . PHP_EOL); } diff --git a/config/database.php b/config/database.php index a4b01139cd..27bc6deb2c 100644 --- a/config/database.php +++ b/config/database.php @@ -57,29 +57,6 @@ PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT => env('MYSQL_ATTR_SSL_VERIFY_SERVER_CERT', true), ]) : [], ], - - /* - | ------------------------------------------------------------------------- - | Test Database Connection - | ------------------------------------------------------------------------- - | - | This connection is used by the integration and HTTP tests for Pterodactyl - | development. Normal users of the Panel do not need to adjust any settings - | in here. - */ - 'testing' => [ - 'driver' => 'mysql', - 'host' => env('TESTING_DB_HOST', '127.0.0.1'), - 'port' => env('TESTING_DB_PORT', '3306'), - 'database' => env('TESTING_DB_DATABASE', 'panel_test'), - 'username' => env('TESTING_DB_USERNAME', 'pterodactyl_test'), - 'password' => env('TESTING_DB_PASSWORD', ''), - 'charset' => 'utf8mb4', - 'collation' => 'utf8mb4_unicode_ci', - 'prefix' => '', - 'strict' => false, - 'timezone' => env('DB_TIMEZONE', Time::getMySQLTimezoneOffset(env('APP_TIMEZONE', 'UTC'))), - ], ], /* diff --git a/phpunit.xml b/phpunit.xml index 227dadb9e7..b3210d5da2 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,17 +1,9 @@ - @@ -19,20 +11,18 @@ - - ./tests/Browser/Processes - - ./tests/Integration + ./tests/Integration - ./tests/Unit + ./tests/Unit - + + From ab37ee8633090470bac0ac0cee85db9e5a2bca67 Mon Sep 17 00:00:00 2001 From: DaneEveritt Date: Wed, 4 May 2022 20:41:32 -0400 Subject: [PATCH 009/458] Remove dusk tests, they're not used --- .env.dusk | 26 ---- .github/workflows/release.yml | 2 +- .gitignore | 1 - composer.json | 1 - composer.lock | 140 +----------------- phpunit.dusk.xml | 21 --- tests/Browser/BrowserTestCase.php | 122 --------------- tests/Browser/Pages/BasePage.php | 19 --- tests/Browser/Pages/Dashboard/AccountPage.php | 41 ----- tests/Browser/Pages/LoginPage.php | 26 ---- .../ForgotPasswordProcessTest.php | 50 ------- .../Authentication/LoginProcessTest.php | 83 ----------- .../Dashboard/AccountEmailProcessTest.php | 67 --------- .../Dashboard/AccountPasswordProcessTest.php | 55 ------- .../Processes/Dashboard/DashboardTestCase.php | 23 --- .../TwoFactorAuthenticationProcessTest.php | 111 -------------- tests/Browser/PterodactylBrowser.php | 55 ------- tests/Browser/console/.gitignore | 2 - tests/Browser/screenshots/.gitignore | 2 - 19 files changed, 2 insertions(+), 845 deletions(-) delete mode 100644 .env.dusk delete mode 100644 phpunit.dusk.xml delete mode 100644 tests/Browser/BrowserTestCase.php delete mode 100644 tests/Browser/Pages/BasePage.php delete mode 100644 tests/Browser/Pages/Dashboard/AccountPage.php delete mode 100644 tests/Browser/Pages/LoginPage.php delete mode 100644 tests/Browser/Processes/Authentication/ForgotPasswordProcessTest.php delete mode 100644 tests/Browser/Processes/Authentication/LoginProcessTest.php delete mode 100644 tests/Browser/Processes/Dashboard/AccountEmailProcessTest.php delete mode 100644 tests/Browser/Processes/Dashboard/AccountPasswordProcessTest.php delete mode 100644 tests/Browser/Processes/Dashboard/DashboardTestCase.php delete mode 100644 tests/Browser/Processes/Dashboard/TwoFactorAuthenticationProcessTest.php delete mode 100644 tests/Browser/PterodactylBrowser.php delete mode 100644 tests/Browser/console/.gitignore delete mode 100644 tests/Browser/screenshots/.gitignore diff --git a/.env.dusk b/.env.dusk deleted file mode 100644 index 237f61d323..0000000000 --- a/.env.dusk +++ /dev/null @@ -1,26 +0,0 @@ -APP_ENV=local -APP_DEBUG=false -APP_KEY=NDWgIKKi9ovNK1PXZpzfNVSBdfCXGb5i -APP_JWT_KEY=test1234 -APP_TIMEZONE=America/Los_Angeles -APP_URL=http://pterodactyl.local - -CACHE_DRIVER=file -SESSION_DRIVER=file - -HASHIDS_SALT=IqRr0g82tCTeuyxGs8RV -HASHIDS_LENGTH=8 - -MAIL_DRIVER=log -MAIL_FROM=support@pterodactyl.io -QUEUE_DRIVER=array - -APP_SERVICE_AUTHOR=testing@pterodactyl.io -MAIL_FROM_NAME="Pterodactyl Panel" -RECAPTCHA_ENABLED=false - -DB_CONNECTION=testing -TESTING_DB_HOST=192.168.1.202 -TESTING_DB_DATABASE=panel_test -TESTING_DB_USERNAME=panel_test -TESTING_DB_PASSWORD=Test1234 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f872744cf3..64e5e68492 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -33,7 +33,7 @@ jobs: - name: Create release archive run: | - rm -rf node_modules/ test/ codecov.yml CODE_OF_CONDUCT.md CONTRIBUTING.md phpunit.dusk.xml phpunit.xml Vagrantfile + rm -rf node_modules/ test/ codecov.yml CODE_OF_CONDUCT.md CONTRIBUTING.md phpunit.xml Vagrantfile tar -czf panel.tar.gz * .env.example .eslintignore .eslintrc.yml .babel-plugin-macrosrc.js - name: Extract changelog diff --git a/.gitignore b/.gitignore index 2ffab75293..cf911a1de9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,6 @@ /vendor *.DS_Store* !.env.ci -!.env.dusk !.env.example .env* .vagrant/* diff --git a/composer.json b/composer.json index e6b48d080d..fa5db6eb0c 100644 --- a/composer.json +++ b/composer.json @@ -47,7 +47,6 @@ "fakerphp/faker": "^1.19", "friendsofphp/php-cs-fixer": "^3.8", "itsgoingd/clockwork": "^5.1", - "laravel/dusk": "^6.23", "mockery/mockery": "^1.5", "nunomaduro/collision": "^5.11", "php-mock/php-mock-phpunit": "^2.6", diff --git a/composer.lock b/composer.lock index 31f205afaa..8fecedfe99 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "75eb69604c51a9ac2dcdf4da1f6d24cb", + "content-hash": "966e12710f76fb744c32e90103b9f823", "packages": [ { "name": "aws/aws-crt-php", @@ -8218,79 +8218,6 @@ ], "time": "2022-04-12T21:35:47+00:00" }, - { - "name": "laravel/dusk", - "version": "v6.23.1", - "source": { - "type": "git", - "url": "https://github.com/laravel/dusk.git", - "reference": "41f6deb42ae42b9b7dae1c32c03cb35d365d3118" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laravel/dusk/zipball/41f6deb42ae42b9b7dae1c32c03cb35d365d3118", - "reference": "41f6deb42ae42b9b7dae1c32c03cb35d365d3118", - "shasum": "" - }, - "require": { - "ext-json": "*", - "ext-zip": "*", - "illuminate/console": "^6.0|^7.0|^8.0|^9.0", - "illuminate/support": "^6.0|^7.0|^8.0|^9.0", - "nesbot/carbon": "^2.0", - "php": "^7.2|^8.0", - "php-webdriver/webdriver": "^1.9.0", - "symfony/console": "^4.3|^5.0|^6.0", - "symfony/finder": "^4.3|^5.0|^6.0", - "symfony/process": "^4.3|^5.0|^6.0", - "vlucas/phpdotenv": "^3.0|^4.0|^5.2" - }, - "require-dev": { - "mockery/mockery": "^1.0", - "orchestra/testbench": "^4.16|^5.17.1|^6.12.1|^7.0", - "phpunit/phpunit": "^7.5.15|^8.4|^9.0" - }, - "suggest": { - "ext-pcntl": "Used to gracefully terminate Dusk when tests are running." - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "6.x-dev" - }, - "laravel": { - "providers": [ - "Laravel\\Dusk\\DuskServiceProvider" - ] - } - }, - "autoload": { - "psr-4": { - "Laravel\\Dusk\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Taylor Otwell", - "email": "taylor@laravel.com" - } - ], - "description": "Laravel Dusk provides simple end-to-end testing and browser automation.", - "keywords": [ - "laravel", - "testing", - "webdriver" - ], - "support": { - "issues": "https://github.com/laravel/dusk/issues", - "source": "https://github.com/laravel/dusk/tree/v6.23.1" - }, - "time": "2022-05-02T14:01:47+00:00" - }, { "name": "mockery/mockery", "version": "1.5.0", @@ -8862,71 +8789,6 @@ }, "time": "2020-02-08T15:44:47+00:00" }, - { - "name": "php-webdriver/webdriver", - "version": "1.12.1", - "source": { - "type": "git", - "url": "https://github.com/php-webdriver/php-webdriver.git", - "reference": "b27ddf458d273c7d4602106fcaf978aa0b7fe15a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-webdriver/php-webdriver/zipball/b27ddf458d273c7d4602106fcaf978aa0b7fe15a", - "reference": "b27ddf458d273c7d4602106fcaf978aa0b7fe15a", - "shasum": "" - }, - "require": { - "ext-curl": "*", - "ext-json": "*", - "ext-zip": "*", - "php": "^5.6 || ~7.0 || ^8.0", - "symfony/polyfill-mbstring": "^1.12", - "symfony/process": "^2.8 || ^3.1 || ^4.0 || ^5.0 || ^6.0" - }, - "replace": { - "facebook/webdriver": "*" - }, - "require-dev": { - "ondram/ci-detector": "^2.1 || ^3.5 || ^4.0", - "php-coveralls/php-coveralls": "^2.4", - "php-mock/php-mock-phpunit": "^1.1 || ^2.0", - "php-parallel-lint/php-parallel-lint": "^1.2", - "phpunit/phpunit": "^5.7 || ^7 || ^8 || ^9", - "squizlabs/php_codesniffer": "^3.5", - "symfony/var-dumper": "^3.3 || ^4.0 || ^5.0 || ^6.0" - }, - "suggest": { - "ext-SimpleXML": "For Firefox profile creation" - }, - "type": "library", - "autoload": { - "files": [ - "lib/Exception/TimeoutException.php" - ], - "psr-4": { - "Facebook\\WebDriver\\": "lib/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "A PHP client for Selenium WebDriver. Previously facebook/webdriver.", - "homepage": "https://github.com/php-webdriver/php-webdriver", - "keywords": [ - "Chromedriver", - "geckodriver", - "php", - "selenium", - "webdriver" - ], - "support": { - "issues": "https://github.com/php-webdriver/php-webdriver/issues", - "source": "https://github.com/php-webdriver/php-webdriver/tree/1.12.1" - }, - "time": "2022-05-03T12:16:34+00:00" - }, { "name": "phpdocumentor/reflection-common", "version": "2.2.0", diff --git a/phpunit.dusk.xml b/phpunit.dusk.xml deleted file mode 100644 index 60392c9324..0000000000 --- a/phpunit.dusk.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - ./tests/Browser - - - - - ./app - - - diff --git a/tests/Browser/BrowserTestCase.php b/tests/Browser/BrowserTestCase.php deleted file mode 100644 index 68a6ddc1bb..0000000000 --- a/tests/Browser/BrowserTestCase.php +++ /dev/null @@ -1,122 +0,0 @@ -make(Kernel::class); - - $kernel->bootstrap(); - $kernel->call('migrate:fresh'); - } - - /** - * Setup tests. - */ - protected function setUp(): void - { - // Don't accidentally run the migrations aganist the non-testing database. Ask me - // how many times I've accidentally dropped my database... - if (env('DB_CONNECTION') !== 'testing') { - throw new BadMethodCallException('Cannot call browser tests using the non-testing database connection.'); - } - - parent::setUp(); - - // Gotta unset this to continue avoiding issues with the validation. - Model::unsetEventDispatcher(); - } - - /** - * Create the RemoteWebDriver instance. - * - * @return \Facebook\WebDriver\Remote\RemoteWebDriver - */ - protected function driver() - { - $options = (new ChromeOptions())->addArguments([ - '--disable-gpu', - '--disable-infobars', - ]); - - return RemoteWebDriver::create( - 'http://host.pterodactyl.local:4444/wd/hub', - DesiredCapabilities::chrome()->setCapability( - ChromeOptions::CAPABILITY, - $options - ) - ); - } - - /** - * Return an instance of the browser to be used for tests. - * - * @param \Facebook\WebDriver\Remote\RemoteWebDriver $driver - * - * @return \Pterodactyl\Tests\Browser\PterodactylBrowser - */ - protected function newBrowser($driver): PterodactylBrowser - { - return new PterodactylBrowser($driver); - } - - /** - * Tear down the test and delete all cookies from the browser instance to address - * instances where the test would be kicked over to the login page. - */ - protected function tearDown(): void - { - /** @var \Pterodactyl\Tests\Browser\PterodactylBrowser $browser */ - foreach (static::$browsers as $browser) { - $browser->driver->manage()->deleteAllCookies(); - } - - parent::tearDown(); - } - - /** - * Return a user model to authenticate aganist and use in the tests. - */ - protected function user(array $attributes = []): User - { - return User::factory()->create(array_merge([ - 'password' => Hash::make(static::$userPassword), - ], $attributes)); - } -} diff --git a/tests/Browser/Pages/BasePage.php b/tests/Browser/Pages/BasePage.php deleted file mode 100644 index c2e451a3d7..0000000000 --- a/tests/Browser/Pages/BasePage.php +++ /dev/null @@ -1,19 +0,0 @@ - '.alert.success[role="alert"]', - '@@error' => '.alert.error[role="alert"]', - ]; - } -} diff --git a/tests/Browser/Pages/Dashboard/AccountPage.php b/tests/Browser/Pages/Dashboard/AccountPage.php deleted file mode 100644 index e1aef4c2a8..0000000000 --- a/tests/Browser/Pages/Dashboard/AccountPage.php +++ /dev/null @@ -1,41 +0,0 @@ - '#update-email-container #grid-email', - '@password' => '#update-email-container #grid-password[type="password"]', - '@submit' => '#update-email-container button[type="submit"]', - - '@current_password' => '#change-password-container #grid-password-current[type="password"]', - '@new_password' => '#change-password-container #grid-password-new[type="password"]', - '@confirm_password' => '#change-password-container #grid-password-new-confirm[type="password"]', - '@submit_password' => '#change-password-container button[type="submit"]', - - '@2fa_button' => '#grid-open-two-factor-modal', - '@2fa_modal' => '.modal-mask #configure-two-factor', - '@2fa_token' => '#configure-two-factor #container-enable-two-factor #grid-two-factor-token[type="number"]', - '@2fa_token_disable' => '#configure-two-factor #container-disable-two-factor #grid-two-factor-token-disable', - '@2fa_enable' => '#configure-two-factor #container-enable-two-factor button[type="submit"]', - '@2fa_disable' => '#configure-two-factor #container-disable-two-factor button.btn-red[type="submit"]', - '@2fa_cancel' => '#configure-two-factor #container-disable-two-factor button.btn-secondary', - ]); - } -} diff --git a/tests/Browser/Pages/LoginPage.php b/tests/Browser/Pages/LoginPage.php deleted file mode 100644 index ea60b0580f..0000000000 --- a/tests/Browser/Pages/LoginPage.php +++ /dev/null @@ -1,26 +0,0 @@ - '#grid-email', - '@username' => '#grid-username', - '@password' => '#grid-password', - '@loginButton' => '#grid-login-button', - '@submitButton' => 'button.btn.btn-jumbo[type="submit"]', - '@forgotPassword' => 'a[href="/auth/password"][aria-label="Forgot password"]', - '@goToLogin' => 'a[href="/auth/login"][aria-label="Go to login"]', - '@alertSuccess' => 'div[role="alert"].success > span.message', - '@alertDanger' => 'div[role="alert"].danger > span.message', - ]; - } -} diff --git a/tests/Browser/Processes/Authentication/ForgotPasswordProcessTest.php b/tests/Browser/Processes/Authentication/ForgotPasswordProcessTest.php deleted file mode 100644 index f6cef86c4e..0000000000 --- a/tests/Browser/Processes/Authentication/ForgotPasswordProcessTest.php +++ /dev/null @@ -1,50 +0,0 @@ -browse(function (PterodactylBrowser $browser) { - $browser->visit(new LoginPage()) - ->assertSee(trans('auth.forgot_password.label')) - ->click('@forgotPassword') - ->waitForLocation('/auth/password') - ->assertFocused('@email') - ->assertSeeIn('.input-open > p.text-xs', trans('auth.forgot_password.label_help')) - ->assertSeeIn('@submitButton', trans('auth.forgot_password.button')) - ->type('@email', 'unassociated@example.com') - ->assertSeeIn('@goToLogin', trans('auth.go_to_login')) - ->press('@submitButton') - ->waitForLocation('/auth/login') - ->assertSeeIn('div[role="alert"].success > span.message', 'We have e-mailed your password reset link!') - ->assertFocused('@username') - ->assertValue('@username', 'unassociated@example.com'); - }); - } - - /** - * Test that you can type in your email address and then click forgot password and have - * the email maintained on the new page. - */ - public function testEmailCarryover() - { - $this->browse(function (PterodactylBrowser $browser) { - $browser->visit(new LoginPage()) - ->type('@username', 'dane@example.com') - ->click('@forgotPassword') - ->waitForLocation('/auth/password') - ->assertFocused('@email') - ->assertValue('@email', 'dane@example.com'); - }); - } -} diff --git a/tests/Browser/Processes/Authentication/LoginProcessTest.php b/tests/Browser/Processes/Authentication/LoginProcessTest.php deleted file mode 100644 index 0806cd7896..0000000000 --- a/tests/Browser/Processes/Authentication/LoginProcessTest.php +++ /dev/null @@ -1,83 +0,0 @@ -user = $this->user(); - } - - /** - * Test that a user can login successfully using their email address. - */ - public function testLoginUsingEmail() - { - $this->browse(function (PterodactylBrowser $browser) { - $browser->visit(new LoginPage()) - ->waitFor('@username') - ->type('@username', $this->user->email) - ->type('@password', self::$userPassword) - ->click('@loginButton') - ->waitForReload() - ->assertPathIs('/') - ->assertAuthenticatedAs($this->user); - }); - } - - /** - * Test that a user can login successfully using their username. - */ - public function testLoginUsingUsername() - { - $this->browse(function (PterodactylBrowser $browser) { - $browser->visit(new LoginPage()) - ->waitFor('@username') - ->type('@username', $this->user->username) - ->type('@password', self::$userPassword) - ->click('@loginButton') - ->waitForReload() - ->assertPathIs('/') - ->assertAuthenticatedAs($this->user); - }); - } - - /** - * Test that entering the wrong password shows the expected error and then allows - * us to login without clearing the username field. - */ - public function testLoginWithErrors() - { - $this->browse(function (PterodactylBrowser $browser) { - $browser->logout() - ->visit(new LoginPage()) - ->waitFor('@username') - ->type('@username', $this->user->email) - ->type('@password', 'invalid') - ->click('@loginButton') - ->waitFor('.alert.error') - ->assertSeeIn('.alert.error', trans('auth.failed')) - ->assertValue('@username', $this->user->email) - ->assertValue('@password', '') - ->assertFocused('@password') - ->type('@password', self::$userPassword) - ->keys('@password', [WebDriverKeys::ENTER]) - ->waitForReload() - ->assertPathIs('/') - ->assertAuthenticatedAs($this->user); - }); - } -} diff --git a/tests/Browser/Processes/Dashboard/AccountEmailProcessTest.php b/tests/Browser/Processes/Dashboard/AccountEmailProcessTest.php deleted file mode 100644 index 6efe4ee032..0000000000 --- a/tests/Browser/Processes/Dashboard/AccountEmailProcessTest.php +++ /dev/null @@ -1,67 +0,0 @@ -browse(function (PterodactylBrowser $browser) { - $browser->loginAs($this->user) - ->visit(new AccountPage()) - ->assertValue('@email', $this->user->email) - ->type('@email', 'new.email@example.com') - ->type('@password', 'Password123') - ->click('@submit') - ->waitFor('@@success') - ->assertSeeIn('@@success', trans('dashboard/account.email.updated')) - ->assertValue('@email', 'new.email@example.com'); - - $this->assertDatabaseHas('users', ['id' => $this->user->id, 'email' => 'new.email@example.com']); - }); - } - - /** - * Test that the validation error message shows up when an invalid email is entered. - */ - public function testInvalidEmailShowsErrors() - { - $this->browse(function (PterodactylBrowser $browser) { - $browser->loginAs($this->user) - ->visit(new AccountPage()) - ->assertMissing('@email ~ .input-help.error') - ->type('@email', 'admin') - ->assertVisible('@email ~ .input-help.error') - ->assertSeeIn('@email ~ .input-help.error', 'The email field must be a valid email.') - ->type('@email', 'admin@example.com') - ->assertMissing('@email ~ .input-help.error'); - }); - } - - /** - * Test that entering the wrong password for an account returns an error. - */ - public function testInvalidPasswordShowsError() - { - $this->browse(function (PterodactylBrowser $browser) { - $browser->loginAs($this->user) - ->visit(new AccountPage()) - ->type('@email', 'new.email@example.com') - ->click('@submit') - ->assertFocused('@password') - ->type('@password', 'test1234') - ->click('@submit') - ->waitFor('@@error') - ->assertSeeIn('@@error', trans('validation.internal.invalid_password')) - ->assertValue('@email', 'new.email@example.com'); - - $this->assertDatabaseMissing('users', ['id' => $this->user->id, 'email' => 'new.email@example.com']); - }); - } -} diff --git a/tests/Browser/Processes/Dashboard/AccountPasswordProcessTest.php b/tests/Browser/Processes/Dashboard/AccountPasswordProcessTest.php deleted file mode 100644 index 4b8a5a3b64..0000000000 --- a/tests/Browser/Processes/Dashboard/AccountPasswordProcessTest.php +++ /dev/null @@ -1,55 +0,0 @@ -browse(function (PterodactylBrowser $browser) { - $browser->loginAs($this->user) - ->visit(new AccountPage()) - ->type('@current_password', self::$userPassword) - ->assertMissing('@new_password ~ .input-help.error') - ->type('@new_password', 'test') - ->assertSeeIn('@new_password ~ .input-help.error', 'The password field must be at least 8 characters.') - ->type('@new_password', 'Test1234') - ->assertMissing('@new_password ~ .input-help.error') - ->assertMissing('@confirm_password ~ .input-help.error') - ->type('@confirm_password', 'test') - ->assertSeeIn('@confirm_password ~ .input-help.error', 'The password value is not valid.') - ->type('@confirm_password', 'Test1234') - ->assertMissing('@confirm_password ~ .input-help.error') - ->click('@submit_password') - ->waitFor('@@success') - ->assertSeeIn('@@success', 'Your password has been updated.') - ->assertInputValue('@current_password', '') - ->assertInputValue('@new_password', '') - ->assertInputValue('@confirm_password', ''); - }); - } - - /** - * Test that invalid passwords result in the expected error message. - */ - public function testInvalidPassword() - { - $this->browse(function (PterodactylBrowser $browser) { - $browser->loginAs($this->user) - ->visit(new AccountPage()) - ->type('@current_password', 'badpassword') - ->type('@new_password', 'testtest') - ->type('@confirm_password', 'testtest') - ->click('@submit_password') - ->waitFor('@@error') - ->assertSeeIn('@@error', trans('validation.internal.invalid_password')) - ->assertInputValue('@current_password', ''); - }); - } -} diff --git a/tests/Browser/Processes/Dashboard/DashboardTestCase.php b/tests/Browser/Processes/Dashboard/DashboardTestCase.php deleted file mode 100644 index efbeb13b4e..0000000000 --- a/tests/Browser/Processes/Dashboard/DashboardTestCase.php +++ /dev/null @@ -1,23 +0,0 @@ -user = $this->user(); - } -} diff --git a/tests/Browser/Processes/Dashboard/TwoFactorAuthenticationProcessTest.php b/tests/Browser/Processes/Dashboard/TwoFactorAuthenticationProcessTest.php deleted file mode 100644 index 463286ddb8..0000000000 --- a/tests/Browser/Processes/Dashboard/TwoFactorAuthenticationProcessTest.php +++ /dev/null @@ -1,111 +0,0 @@ -browse(function (PterodactylBrowser $browser) { - $browser->loginAs($this->user) - ->visit(new AccountPage()) - ->assertMissing('.modal-mask') - ->click('@2fa_button') - ->waitFor('@2fa_modal') - ->pause(500)// seems to fix fragile test - ->clickPosition(100, 100) - ->waitUntilMissing('@2fa_modal') - ->click('@2fa_button') - ->waitFor('@2fa_modal') - ->click('svg[role="button"][aria-label="Close modal"]') - ->waitUntilMissing('@2fa_modal') - ->click('@2fa_button') - ->waitFor('@2fa_modal') - ->keys('', [WebDriverKeys::ESCAPE]) - ->waitUntilMissing('@2fa_modal'); - }); - } - - /** - * Test that a user that does not have two-factor enabled can enable it on their account. - */ - public function testTwoFactorCanBeEnabled() - { - $this->browse(function (PterodactylBrowser $browser) { - $browser->loginAs($this->user) - ->visit(new AccountPage()) - ->click('@2fa_button') - ->waitForText(trans('dashboard/account.two_factor.setup.title')) - ->assertFocused('@2fa_token') - ->waitFor('#grid-qr-code') - ->assertSee(trans('dashboard/account.two_factor.setup.help')); - - // Grab information from the database so we can ensure the correct things are showing up. - // Also because we need to generate a code to send through and activate it with. - $updated = $this->user->fresh(); - - $secret = Crypt::decrypt($updated->totp_secret); - $code = (new Google2FA())->getCurrentOtp($secret); - - $browser->assertSeeIn('code', $secret) - ->assertVisible('@2fa_enable[disabled="disabled"]') - ->assertMissing('@2fa_token ~ .input-help.error') - ->type('@2fa_token', '12') - ->assertSeeIn('@2fa_token ~ .input-help.error', 'The token length must be 6.') - ->type('@2fa_token', $code) - ->assertMissing('@2fa_token ~ .input-help.error') - ->click('@2fa_enable') - ->waitUntilMissing('@2fa_modal') - ->assertSeeIn('@@success', trans('dashboard/account.two_factor.enabled')); - - $this->assertDatabaseHas('users', ['id' => $this->user->id, 'use_totp' => 1]); - }); - } - - /** - * Test that a user can disable two-factor authentication on thier account. - */ - public function testTwoFactorCanBeDisabled() - { - $secret = (new Google2FA())->generateSecretKey(16); - - $this->user->update([ - 'use_totp' => true, - 'totp_secret' => Crypt::encrypt($secret), - ]); - - $this->browse(function (PterodactylBrowser $browser) use ($secret) { - $browser->loginAs($this->user) - ->visit(new AccountPage()) - ->click('@2fa_button') - ->waitForText(trans('dashboard/account.two_factor.disable.title')) - ->click('@2fa_cancel') - ->waitUntilMissing('@2fa_modal') - ->click('@2fa_button') - ->waitForText(trans('dashboard/account.two_factor.disable.title')) - ->assertVisible('@2fa_disable[disabled="disabled"]') - ->assertVisible('@2fa_cancel') - ->assertFocused('@2fa_token_disable') - ->assertMissing('@2fa_token_disable ~ .input-help.error') - ->type('@2fa_token_disable', '12') - ->assertSeeIn('@2fa_token_disable ~ .input-help.error', 'The token length must be 6.'); - - $token = (new Google2FA())->getCurrentOtp($secret); - - $browser->type('@2fa_token_disable', $token) - ->assertMissing('@2fa_token_disable ~ .input-help.error') - ->click('@2fa_disable') - ->waitUntilMissing('@2fa_modal') - ->assertSeeIn('@@success', trans('dashboard/account.two_factor.disabled')); - }); - } -} diff --git a/tests/Browser/PterodactylBrowser.php b/tests/Browser/PterodactylBrowser.php deleted file mode 100644 index 9136a6a65a..0000000000 --- a/tests/Browser/PterodactylBrowser.php +++ /dev/null @@ -1,55 +0,0 @@ -driver->getMouse()->mouseMove(null, $x, $y)->click(); - - return $this; - } - - /** - * Perform a case insensitive search for a string in the body. - * - * @param string $text - * - * @return \Pterodactyl\Tests\Browser\PterodactylBrowser - */ - public function assertSee($text) - { - return $this->assertSeeIn('', $text); - } - - /** - * Perform a case insensitive search for a string in a given selector. - * - * @param string $selector - * @param string $text - * - * @return \Pterodactyl\Tests\Browser\PterodactylBrowser - */ - public function assertSeeIn($selector, $text) - { - $fullSelector = $this->resolver->format($selector); - $element = $this->resolver->findOrFail($selector); - - PHPUnit::assertTrue( - Str::contains(mb_strtolower($element->getText()), mb_strtolower($text)), - "Did not see expected text [{$text}] within element [{$fullSelector}] using case-insensitive search." - ); - - return $this; - } -} diff --git a/tests/Browser/console/.gitignore b/tests/Browser/console/.gitignore deleted file mode 100644 index d6b7ef32c8..0000000000 --- a/tests/Browser/console/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore diff --git a/tests/Browser/screenshots/.gitignore b/tests/Browser/screenshots/.gitignore deleted file mode 100644 index d6b7ef32c8..0000000000 --- a/tests/Browser/screenshots/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore From b07fdc100c937f869d4eb5893bfc39188e6afcd0 Mon Sep 17 00:00:00 2001 From: DaneEveritt Date: Wed, 4 May 2022 20:41:53 -0400 Subject: [PATCH 010/458] Don't run schedules when a server is suspended or installing; closes #4008 --- .../Schedule/ProcessRunnableCommand.php | 5 ++- app/Jobs/Schedule/RunTaskJob.php | 10 ++++++ .../Jobs/Schedule/RunTaskJobTest.php | 32 +++++++++++++++++++ 3 files changed, 46 insertions(+), 1 deletion(-) diff --git a/app/Console/Commands/Schedule/ProcessRunnableCommand.php b/app/Console/Commands/Schedule/ProcessRunnableCommand.php index fa335c2bd5..b334fbb136 100644 --- a/app/Console/Commands/Schedule/ProcessRunnableCommand.php +++ b/app/Console/Commands/Schedule/ProcessRunnableCommand.php @@ -7,6 +7,7 @@ use Illuminate\Console\Command; use Pterodactyl\Models\Schedule; use Illuminate\Support\Facades\Log; +use Illuminate\Database\Eloquent\Builder; use Pterodactyl\Services\Schedules\ProcessScheduleService; class ProcessRunnableCommand extends Command @@ -26,7 +27,9 @@ class ProcessRunnableCommand extends Command */ public function handle() { - $schedules = Schedule::query()->with('tasks') + $schedules = Schedule::query() + ->with('tasks') + ->whereRelation('server', fn (Builder $builder) => $builder->whereNull('status')) ->where('is_active', true) ->where('is_processing', false) ->whereRaw('next_run_at <= NOW()') diff --git a/app/Jobs/Schedule/RunTaskJob.php b/app/Jobs/Schedule/RunTaskJob.php index fa62bc0043..0b0f9e0754 100644 --- a/app/Jobs/Schedule/RunTaskJob.php +++ b/app/Jobs/Schedule/RunTaskJob.php @@ -61,6 +61,16 @@ public function handle( } $server = $this->task->server; + // If we made it to this point and the server status is not null it means the + // server was likely suspended or marked as reinstalling after the schedule + // was queued up. Just end the task right now — this should be a very rare + // condition. + if (!is_null($server->status)) { + $this->failed(); + + return; + } + // Perform the provided task against the daemon. try { switch ($this->task->action) { diff --git a/tests/Integration/Jobs/Schedule/RunTaskJobTest.php b/tests/Integration/Jobs/Schedule/RunTaskJobTest.php index 68249dc43c..9a5dfcea90 100644 --- a/tests/Integration/Jobs/Schedule/RunTaskJobTest.php +++ b/tests/Integration/Jobs/Schedule/RunTaskJobTest.php @@ -3,6 +3,8 @@ namespace Pterodactyl\Tests\Integration\Jobs\Schedule; use Mockery; +use Carbon\Carbon; +use DateTimeInterface; use Carbon\CarbonImmutable; use GuzzleHttp\Psr7\Request; use Pterodactyl\Models\Task; @@ -146,6 +148,36 @@ public function testExceptionDuringRunIsHandledCorrectly(bool $continueOnFailure } } + /** + * Test that a schedule is not executed if the server is suspended. + * + * @see https://github.com/pterodactyl/panel/issues/4008 + */ + public function testTaskIsNotRunIfServerIsSuspended() + { + $server = $this->createServerModel([ + 'status' => Server::STATUS_SUSPENDED, + ]); + + $schedule = Schedule::factory()->for($server)->create([ + 'last_run_at' => Carbon::now()->subHour(), + ]); + + $task = Task::factory()->for($schedule)->create([ + 'action' => Task::ACTION_POWER, + 'payload' => 'start', + ]); + + Bus::dispatchNow(new RunTaskJob($task)); + + $task->refresh(); + $schedule->refresh(); + + $this->assertFalse($task->is_queued); + $this->assertFalse($schedule->is_processing); + $this->assertTrue(Carbon::now()->isSameAs(DateTimeInterface::ATOM, $schedule->last_run_at)); + } + /** * @return array */ From c751ce7f44685d8fe5328d1e206643706a85a368 Mon Sep 17 00:00:00 2001 From: DaneEveritt Date: Sat, 7 May 2022 14:17:10 -0400 Subject: [PATCH 011/458] Allow more values for remote field when creating a database; closes #3842 --- .../Servers/Databases/StoreDatabaseRequest.php | 5 +++-- app/Models/Database.php | 2 +- app/Models/Model.php | 10 ++++++++++ .../server/databases/CreateDatabaseButton.tsx | 15 ++++++++------- storage/clockwork/.gitignore | 0 5 files changed, 22 insertions(+), 10 deletions(-) mode change 100644 => 100755 storage/clockwork/.gitignore diff --git a/app/Http/Requests/Api/Client/Servers/Databases/StoreDatabaseRequest.php b/app/Http/Requests/Api/Client/Servers/Databases/StoreDatabaseRequest.php index b8f12bd8ac..5258062f7c 100644 --- a/app/Http/Requests/Api/Client/Servers/Databases/StoreDatabaseRequest.php +++ b/app/Http/Requests/Api/Client/Servers/Databases/StoreDatabaseRequest.php @@ -5,6 +5,7 @@ use Webmozart\Assert\Assert; use Pterodactyl\Models\Server; use Illuminate\Validation\Rule; +use Pterodactyl\Models\Database; use Pterodactyl\Models\Permission; use Illuminate\Database\Query\Builder; use Pterodactyl\Contracts\Http\ClientPermissionsRequest; @@ -28,7 +29,7 @@ public function rules(): array 'database' => [ 'required', 'alpha_dash', - 'min:1', + 'min:3', 'max:48', // Yes, I am aware that you could have the same database name across two unique hosts. However, // I don't really care about that for this validation. We just want to make sure it is unique to @@ -38,7 +39,7 @@ public function rules(): array ->where('database', DatabaseManagementService::generateUniqueDatabaseName($this->input('database'), $server->id)); }), ], - 'remote' => 'required|string|regex:/^[0-9%.]{1,15}$/', + 'remote' => Database::getRulesForField('remote'), ]; } diff --git a/app/Models/Database.php b/app/Models/Database.php index b09545da80..ebe8867933 100644 --- a/app/Models/Database.php +++ b/app/Models/Database.php @@ -67,7 +67,7 @@ class Database extends Model 'database' => 'required|string|alpha_dash|between:3,48', 'username' => 'string|alpha_dash|between:3,100', 'max_connections' => 'nullable|integer', - 'remote' => 'required|string|regex:/^[0-9%.]{1,15}$/', + 'remote' => 'required|string|regex:/^[\w\-\/.%:]+$/', 'password' => 'string', ]; diff --git a/app/Models/Model.php b/app/Models/Model.php index e5e7fb976b..5eaa1fa68b 100644 --- a/app/Models/Model.php +++ b/app/Models/Model.php @@ -3,6 +3,7 @@ namespace Pterodactyl\Models; use Illuminate\Support\Str; +use Illuminate\Support\Arr; use Illuminate\Validation\Rule; use Illuminate\Container\Container; use Illuminate\Contracts\Validation\Factory; @@ -111,6 +112,15 @@ public static function getRules() return $rules; } + /** + * Returns the rules for a specific field. If the field is not found an empty + * array is returned. + */ + public static function getRulesForField(string $field): array + { + return Arr::get(static::getRules(), $field) ?? []; + } + /** * Returns the rules associated with the model, specifically for updating the given model * rather than just creating it. diff --git a/resources/scripts/components/server/databases/CreateDatabaseButton.tsx b/resources/scripts/components/server/databases/CreateDatabaseButton.tsx index c3b47250f0..0fdeb4fe9a 100644 --- a/resources/scripts/components/server/databases/CreateDatabaseButton.tsx +++ b/resources/scripts/components/server/databases/CreateDatabaseButton.tsx @@ -21,10 +21,8 @@ const schema = object().shape({ .required('A database name must be provided.') .min(3, 'Database name must be at least 3 characters.') .max(48, 'Database name must not exceed 48 characters.') - .matches(/^[A-Za-z0-9_\-.]{3,48}$/, 'Database name should only contain alphanumeric characters, underscores, dashes, and/or periods.'), - connectionsFrom: string() - .required('A connection value must be provided.') - .matches(/^([0-9]{1,3}|%)(\.([0-9]{1,3}|%))?(\.([0-9]{1,3}|%))?(\.([0-9]{1,3}|%))?$/, 'A valid connection address must be provided.'), + .matches(/^[\w\-.]{3,48}$/, 'Database name should only contain alphanumeric characters, underscores, dashes, and/or periods.'), + connectionsFrom: string().matches(/^[\w\-/.%:]+$/, 'A valid host address must be provided.'), }); export default () => { @@ -36,7 +34,10 @@ export default () => { const submit = (values: Values, { setSubmitting }: FormikHelpers) => { clearFlashes('database:create'); - createServerDatabase(uuid, { ...values }) + createServerDatabase(uuid, { + databaseName: values.databaseName, + connectionsFrom: values.connectionsFrom || '%', + }) .then(database => { appendDatabase(database); setVisible(false); @@ -51,7 +52,7 @@ export default () => { <> { @@ -81,7 +82,7 @@ export default () => { id={'connections_from'} name={'connectionsFrom'} label={'Connections From'} - description={'Where connections should be allowed from. Use % for wildcards.'} + description={'Where connections should be allowed from. Leave blank to allow connections from anywhere.'} />
diff --git a/storage/clockwork/.gitignore b/storage/clockwork/.gitignore old mode 100644 new mode 100755 From e88d24e0db051a104c00581acf3b53b6bf1e8f09 Mon Sep 17 00:00:00 2001 From: DaneEveritt Date: Sat, 7 May 2022 15:05:28 -0400 Subject: [PATCH 012/458] Don't allow allocations to be deleted by users if no limit is defined; closes #3703 --- .../Servers/NetworkAllocationController.php | 6 ++++ app/Models/Allocation.php | 18 +++++++++++ database/Factories/AllocationFactory.php | 9 ++++++ .../server/network/NetworkContainer.tsx | 30 ++++++++++--------- .../Client/ClientApiIntegrationTestCase.php | 1 + .../Allocation/DeleteAllocationTest.php | 18 +++++++++++ .../NetworkAllocationControllerTest.php | 5 ---- 7 files changed, 68 insertions(+), 19 deletions(-) diff --git a/app/Http/Controllers/Api/Client/Servers/NetworkAllocationController.php b/app/Http/Controllers/Api/Client/Servers/NetworkAllocationController.php index 855cb6780e..a24dcab91f 100644 --- a/app/Http/Controllers/Api/Client/Servers/NetworkAllocationController.php +++ b/app/Http/Controllers/Api/Client/Servers/NetworkAllocationController.php @@ -120,6 +120,12 @@ public function store(NewAllocationRequest $request, Server $server): array */ public function delete(DeleteAllocationRequest $request, Server $server, Allocation $allocation) { + // Don't allow the deletion of allocations if the server does not have an + // allocation limit set. + if (empty($server->allocation_limit)) { + throw new DisplayException('You cannot delete allocations for this server: no allocation limit is set.'); + } + if ($allocation->id === $server->allocation_id) { throw new DisplayException('You cannot delete the primary allocation for this server.'); } diff --git a/app/Models/Allocation.php b/app/Models/Allocation.php index 9363b40634..49673921b1 100644 --- a/app/Models/Allocation.php +++ b/app/Models/Allocation.php @@ -3,6 +3,8 @@ namespace Pterodactyl\Models; /** + * Pterodactyl\Models\Allocation. + * * @property int $id * @property int $node_id * @property string $ip @@ -16,6 +18,22 @@ * @property bool $has_alias * @property \Pterodactyl\Models\Server|null $server * @property \Pterodactyl\Models\Node $node + * @property string $hashid + * + * @method static \Database\Factories\AllocationFactory factory(...$parameters) + * @method static \Illuminate\Database\Eloquent\Builder|Allocation newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|Allocation newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|Allocation query() + * @method static \Illuminate\Database\Eloquent\Builder|Allocation whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|Allocation whereId($value) + * @method static \Illuminate\Database\Eloquent\Builder|Allocation whereIp($value) + * @method static \Illuminate\Database\Eloquent\Builder|Allocation whereIpAlias($value) + * @method static \Illuminate\Database\Eloquent\Builder|Allocation whereNodeId($value) + * @method static \Illuminate\Database\Eloquent\Builder|Allocation whereNotes($value) + * @method static \Illuminate\Database\Eloquent\Builder|Allocation wherePort($value) + * @method static \Illuminate\Database\Eloquent\Builder|Allocation whereServerId($value) + * @method static \Illuminate\Database\Eloquent\Builder|Allocation whereUpdatedAt($value) + * @mixin \Eloquent */ class Allocation extends Model { diff --git a/database/Factories/AllocationFactory.php b/database/Factories/AllocationFactory.php index 679c8c03eb..9ed42e3ef8 100644 --- a/database/Factories/AllocationFactory.php +++ b/database/Factories/AllocationFactory.php @@ -2,6 +2,7 @@ namespace Database\Factories; +use Pterodactyl\Models\Server; use Pterodactyl\Models\Allocation; use Illuminate\Database\Eloquent\Factories\Factory; @@ -24,4 +25,12 @@ public function definition(): array 'port' => $this->faker->unique()->randomNumber(5), ]; } + + /** + * Attaches the allocation to a specific server model. + */ + public function forServer(Server $server): self + { + return $this->for($server)->for($server->node); + } } diff --git a/resources/scripts/components/server/network/NetworkContainer.tsx b/resources/scripts/components/server/network/NetworkContainer.tsx index 0bb6bd42a1..ec4406da1f 100644 --- a/resources/scripts/components/server/network/NetworkContainer.tsx +++ b/resources/scripts/components/server/network/NetworkContainer.tsx @@ -66,20 +66,22 @@ const NetworkContainer = () => { /> )) } - - -
-

- You are currently using {data.length} of {allocationLimit} allowed allocations for this - server. -

- {allocationLimit > data.length && - - } -
-
+ {allocationLimit > 0 && + + +
+

+ You are currently using {data.length} of {allocationLimit} allowed allocations for + this server. +

+ {allocationLimit > data.length && + + } +
+
+ } } diff --git a/tests/Integration/Api/Client/ClientApiIntegrationTestCase.php b/tests/Integration/Api/Client/ClientApiIntegrationTestCase.php index 80b397ca82..22f941807b 100644 --- a/tests/Integration/Api/Client/ClientApiIntegrationTestCase.php +++ b/tests/Integration/Api/Client/ClientApiIntegrationTestCase.php @@ -89,6 +89,7 @@ protected function link($model, $append = null): string * is assumed that the user is actually a subuser of the server. * * @param string[] $permissions + * @return array{\Pterodactyl\Models\User, \Pterodactyl\Models\Server} */ protected function generateTestAccount(array $permissions = []): array { diff --git a/tests/Integration/Api/Client/Server/Allocation/DeleteAllocationTest.php b/tests/Integration/Api/Client/Server/Allocation/DeleteAllocationTest.php index c986de784a..42db6eef72 100644 --- a/tests/Integration/Api/Client/Server/Allocation/DeleteAllocationTest.php +++ b/tests/Integration/Api/Client/Server/Allocation/DeleteAllocationTest.php @@ -19,6 +19,7 @@ public function testAllocationCanBeDeletedFromServer(array $permission) { /** @var \Pterodactyl\Models\Server $server */ [$user, $server] = $this->generateTestAccount($permission); + $server->update(['allocation_limit' => 2]); /** @var \Pterodactyl\Models\Allocation $allocation */ $allocation = Allocation::factory()->create([ @@ -60,6 +61,7 @@ public function testErrorIsReturnedIfAllocationIsPrimary() { /** @var \Pterodactyl\Models\Server $server */ [$user, $server] = $this->generateTestAccount(); + $server->update(['allocation_limit' => 2]); $this->actingAs($user)->deleteJson($this->link($server->allocation)) ->assertStatus(Response::HTTP_BAD_REQUEST) @@ -67,6 +69,22 @@ public function testErrorIsReturnedIfAllocationIsPrimary() ->assertJsonPath('errors.0.detail', 'You cannot delete the primary allocation for this server.'); } + public function testAllocationCannotBeDeletedIfServerLimitIsNotDefined() + { + [$user, $server] = $this->generateTestAccount(); + + /** @var \Pterodactyl\Models\Allocation $allocation */ + $allocation = Allocation::factory()->forServer($server)->create(['notes' => 'Test notes']); + + $this->actingAs($user)->deleteJson($this->link($allocation)) + ->assertStatus(400) + ->assertJsonPath('errors.0.detail', 'You cannot delete allocations for this server: no allocation limit is set.'); + + $allocation->refresh(); + $this->assertNotNull($allocation->notes); + $this->assertEquals($server->id, $allocation->server_id); + } + /** * Test that an allocation cannot be deleted if it does not belong to the server instance. */ diff --git a/tests/Integration/Api/Client/Server/NetworkAllocationControllerTest.php b/tests/Integration/Api/Client/Server/NetworkAllocationControllerTest.php index 9741af9740..336b4e037b 100644 --- a/tests/Integration/Api/Client/Server/NetworkAllocationControllerTest.php +++ b/tests/Integration/Api/Client/Server/NetworkAllocationControllerTest.php @@ -137,9 +137,4 @@ public function updatePermissionsDataProvider() { return [[[]], [[Permission::ACTION_ALLOCATION_UPDATE]]]; } - - public function deletePermissionsDataProvider() - { - return [[[]], [[Permission::ACTION_ALLOCATION_DELETE]]]; - } } From 1ae98604a4790da45ef99d10fedec4d113420bbc Mon Sep 17 00:00:00 2001 From: DaneEveritt Date: Sat, 7 May 2022 15:25:50 -0400 Subject: [PATCH 013/458] Mark the 2FA field as a one-time-password field for autocomplete; closes #4038 --- resources/scripts/components/auth/LoginCheckpointContainer.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/scripts/components/auth/LoginCheckpointContainer.tsx b/resources/scripts/components/auth/LoginCheckpointContainer.tsx index df2018ee73..ac2f1c749d 100644 --- a/resources/scripts/components/auth/LoginCheckpointContainer.tsx +++ b/resources/scripts/components/auth/LoginCheckpointContainer.tsx @@ -39,6 +39,7 @@ const LoginCheckpointContainer = () => { : 'Enter the two-factor token generated by your device.' } type={'text'} + autoComplete={'one-time-code'} autoFocus />
From 634b80ed42cfcb500c55ec024aa386b5f949675f Mon Sep 17 00:00:00 2001 From: DaneEveritt Date: Sat, 7 May 2022 16:16:11 -0400 Subject: [PATCH 014/458] Add support for filtering allocations to determine if they're assigned or not; closes #3872 --- .../Application/Nodes/AllocationController.php | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/app/Http/Controllers/Api/Application/Nodes/AllocationController.php b/app/Http/Controllers/Api/Application/Nodes/AllocationController.php index 5a66e81756..ecc7e1cd4f 100644 --- a/app/Http/Controllers/Api/Application/Nodes/AllocationController.php +++ b/app/Http/Controllers/Api/Application/Nodes/AllocationController.php @@ -5,6 +5,9 @@ use Pterodactyl\Models\Node; use Illuminate\Http\JsonResponse; use Pterodactyl\Models\Allocation; +use Spatie\QueryBuilder\QueryBuilder; +use Spatie\QueryBuilder\AllowedFilter; +use Illuminate\Database\Eloquent\Builder; use Pterodactyl\Services\Allocations\AssignmentService; use Pterodactyl\Services\Allocations\AllocationDeletionService; use Pterodactyl\Transformers\Api\Application\AllocationTransformer; @@ -43,7 +46,20 @@ public function __construct( */ public function index(GetAllocationsRequest $request, Node $node): array { - $allocations = $node->allocations()->paginate($request->query('per_page') ?? 50); + $allocations = QueryBuilder::for($node->allocations()) + ->allowedFilters([ + AllowedFilter::exact('ip'), + AllowedFilter::exact('port'), + 'ip_alias', + AllowedFilter::callback('server_id', function (Builder $builder, $value) { + if (empty($value) || is_bool($value) || !ctype_digit((string) $value)) { + return $builder->whereNull('server_id'); + } + + return $builder->where('server_id', $value); + }), + ]) + ->paginate($request->query('per_page') ?? 50); return $this->fractal->collection($allocations) ->transformWith($this->getTransformer(AllocationTransformer::class)) From 53207abcb356e88ea23e9cf07422d891b68affbf Mon Sep 17 00:00:00 2001 From: DaneEveritt Date: Sat, 7 May 2022 16:52:58 -0400 Subject: [PATCH 015/458] Add base model layout from V2 for frontend --- package.json | 1 + resources/scripts/api/definitions/index.d.ts | 32 +++++++++++++++++++ .../scripts/api/definitions/user/index.ts | 2 ++ .../scripts/api/definitions/user/models.d.ts | 2 ++ .../api/definitions/user/transformers.ts | 5 +++ tsconfig.json | 3 ++ webpack.config.js | 1 + yarn.lock | 5 +++ 8 files changed, 51 insertions(+) create mode 100644 resources/scripts/api/definitions/index.d.ts create mode 100644 resources/scripts/api/definitions/user/index.ts create mode 100644 resources/scripts/api/definitions/user/models.d.ts create mode 100644 resources/scripts/api/definitions/user/transformers.ts diff --git a/package.json b/package.json index 1f3df5673e..10cd3d0c1e 100644 --- a/package.json +++ b/package.json @@ -97,6 +97,7 @@ "style-loader": "^1.2.1", "svg-url-loader": "^6.0.0", "terser-webpack-plugin": "^3.0.6", + "ts-essentials": "^9.1.2", "twin.macro": "^2.0.7", "typescript": "^4.2.4", "webpack": "^4.43.0", diff --git a/resources/scripts/api/definitions/index.d.ts b/resources/scripts/api/definitions/index.d.ts new file mode 100644 index 0000000000..68925c8633 --- /dev/null +++ b/resources/scripts/api/definitions/index.d.ts @@ -0,0 +1,32 @@ +import { MarkRequired } from 'ts-essentials'; + +export type UUID = string; + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface Model {} + +interface ModelWithRelationships extends Model { + relationships: Record; +} + +/** + * Allows a model to have optional relationships that are marked as being + * present in a given pathway. This allows different API calls to specify the + * "completeness" of a response object without having to make every API return + * the same information, or every piece of logic do explicit null checking. + * + * Example: + * >> const user: WithLoaded = {}; + * >> // "user.servers" is no longer potentially undefined. + */ +type WithLoaded = M & { + relationships: MarkRequired; +} + +/** + * Helper type that allows you to infer the type of an object by giving + * it the specific API request function with a return type. For example: + * + * type Egg = InferModel; + */ +export type InferModel any> = ReturnType extends Promise ? U : T; diff --git a/resources/scripts/api/definitions/user/index.ts b/resources/scripts/api/definitions/user/index.ts new file mode 100644 index 0000000000..7d8db7094e --- /dev/null +++ b/resources/scripts/api/definitions/user/index.ts @@ -0,0 +1,2 @@ +export * from './models.d'; +export { default as Transformers, MetaTransformers } from './transformers'; diff --git a/resources/scripts/api/definitions/user/models.d.ts b/resources/scripts/api/definitions/user/models.d.ts new file mode 100644 index 0000000000..d462cadf8a --- /dev/null +++ b/resources/scripts/api/definitions/user/models.d.ts @@ -0,0 +1,2 @@ +// empty export +export type _T = string; diff --git a/resources/scripts/api/definitions/user/transformers.ts b/resources/scripts/api/definitions/user/transformers.ts new file mode 100644 index 0000000000..a69ad708d7 --- /dev/null +++ b/resources/scripts/api/definitions/user/transformers.ts @@ -0,0 +1,5 @@ +export default class Transformers { +} + +export class MetaTransformers { +} diff --git a/tsconfig.json b/tsconfig.json index 26423ecd66..578a73e315 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -22,6 +22,9 @@ "@/*": [ "./resources/scripts/*" ], + "@definitions/*": [ + "./resources/scripts/api/definitions/*" + ], "@feature/*": [ "./resources/scripts/components/server/features/*" ] diff --git a/webpack.config.js b/webpack.config.js index 777b0be1b3..8a9b766096 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -60,6 +60,7 @@ module.exports = { extensions: ['.ts', '.tsx', '.js', '.json'], alias: { '@': path.join(__dirname, '/resources/scripts'), + '@definitions': path.join(__dirname, '/resources/scripts/api/definitions'), '@feature': path.join(__dirname, '/resources/scripts/components/server/features'), }, symlinks: false, diff --git a/yarn.lock b/yarn.lock index 9c67ff2468..4b67c3662e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7481,6 +7481,11 @@ tryer@^1.0.1: resolved "https://registry.yarnpkg.com/tryer/-/tryer-1.0.1.tgz#f2c85406800b9b0f74c9f7465b81eaad241252f8" integrity sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA== +ts-essentials@^9.1.2: + version "9.1.2" + resolved "https://registry.yarnpkg.com/ts-essentials/-/ts-essentials-9.1.2.tgz#46db6944b73b4cd603f3d959ef1123c16ba56f59" + integrity sha512-EaSmXsAhEiirrTY1Oaa7TSpei9dzuCuFPmjKRJRPamERYtfaGS8/KpOSbjergLz/Y76/aZlV9i/krgzsuWEBbg== + ts-toolbelt@^8.0.7: version "8.0.7" resolved "https://registry.yarnpkg.com/ts-toolbelt/-/ts-toolbelt-8.0.7.tgz#4dad2928831a811ee17dbdab6eb1919fc0a295bf" From c8faf640592a5753d110a556dc3525edddacf606 Mon Sep 17 00:00:00 2001 From: DaneEveritt Date: Sat, 7 May 2022 17:45:22 -0400 Subject: [PATCH 016/458] Support naming docker images on eggs; closes #4052 Bumps PTDL_v1 export images to PTDL_v2, updates the Minecraft specific eggs to use named images. --- .../Controllers/Admin/Nests/EggController.php | 39 ++-- .../Api/Client/Servers/SettingsController.php | 2 +- .../Settings/SetDockerImageRequest.php | 2 +- app/Models/Egg.php | 7 +- .../Eggs/Sharing/EggExporterService.php | 6 +- .../Eggs/Sharing/EggImporterService.php | 48 +++-- .../Api/Application/EggTransformer.php | 3 +- .../eggs/minecraft/egg-bungeecord.json | 24 +-- .../eggs/minecraft/egg-forge-minecraft.json | 28 +-- .../Seeders/eggs/minecraft/egg-paper.json | 30 ++-- .../minecraft/egg-sponge--sponge-vanilla.json | 22 +-- .../eggs/minecraft/egg-vanilla-minecraft.json | 24 +-- ...migrate_egg_images_array_to_new_format.php | 40 +++++ resources/scripts/api/swr/getServerStartup.ts | 8 +- .../server/startup/StartupContainer.tsx | 14 +- resources/views/admin/eggs/view.blade.php | 9 +- .../Services/Api/KeyCreationServiceTest.php | 167 ------------------ 17 files changed, 212 insertions(+), 261 deletions(-) create mode 100644 database/migrations/2022_05_07_165334_migrate_egg_images_array_to_new_format.php delete mode 100644 tests/Unit/Services/Api/KeyCreationServiceTest.php diff --git a/app/Http/Controllers/Admin/Nests/EggController.php b/app/Http/Controllers/Admin/Nests/EggController.php index f8d3655153..11f9c77ad8 100644 --- a/app/Http/Controllers/Admin/Nests/EggController.php +++ b/app/Http/Controllers/Admin/Nests/EggController.php @@ -74,11 +74,7 @@ public function create(): View public function store(EggFormRequest $request): RedirectResponse { $data = $request->normalize(); - if (!empty($data['docker_images']) && !is_array($data['docker_images'])) { - $data['docker_images'] = array_map(function ($value) { - return trim($value); - }, explode("\n", $data['docker_images'])); - } + $data['docker_images'] = $this->normalizeDockerImages($data['docker_images'] ?? null); $egg = $this->creationService->handle($data); $this->alert->success(trans('admin/nests.eggs.notices.egg_created'))->flash(); @@ -91,7 +87,14 @@ public function store(EggFormRequest $request): RedirectResponse */ public function view(Egg $egg): View { - return view('admin.eggs.view', ['egg' => $egg]); + return view('admin.eggs.view', [ + 'egg' => $egg, + 'images' => array_map( + fn ($key, $value) => $key === $value ? $value : "$key|$value", + array_keys($egg->docker_images), + $egg->docker_images, + ), + ]); } /** @@ -104,11 +107,7 @@ public function view(Egg $egg): View public function update(EggFormRequest $request, Egg $egg): RedirectResponse { $data = $request->normalize(); - if (!empty($data['docker_images']) && !is_array($data['docker_images'])) { - $data['docker_images'] = array_map(function ($value) { - return trim($value); - }, explode("\n", $data['docker_images'])); - } + $data['docker_images'] = $this->normalizeDockerImages($data['docker_images'] ?? null); $this->updateService->handle($egg, $data); $this->alert->success(trans('admin/nests.eggs.notices.updated'))->flash(); @@ -129,4 +128,22 @@ public function destroy(Egg $egg): RedirectResponse return redirect()->route('admin.nests.view', $egg->nest_id); } + + /** + * Normalizes a string of docker image data into the expected egg format. + */ + protected function normalizeDockerImages(string $input = null): array + { + $data = array_map(fn ($value) => trim($value), explode("\n", $input ?? '')); + + $images = []; + // Iterate over the image data provided and convert it into a name => image + // pairing that is used to improve the display on the front-end. + foreach ($data as $value) { + $parts = explode('|', $value, 2); + $images[$parts[0]] = empty($parts[1]) ? $parts[0] : $parts[1]; + } + + return $images; + } } diff --git a/app/Http/Controllers/Api/Client/Servers/SettingsController.php b/app/Http/Controllers/Api/Client/Servers/SettingsController.php index 110cd0868f..a7fbb8a75a 100644 --- a/app/Http/Controllers/Api/Client/Servers/SettingsController.php +++ b/app/Http/Controllers/Api/Client/Servers/SettingsController.php @@ -78,7 +78,7 @@ public function reinstall(ReinstallServerRequest $request, Server $server) */ public function dockerImage(SetDockerImageRequest $request, Server $server) { - if (!in_array($server->image, $server->egg->docker_images)) { + if (!in_array($server->image, array_values($server->egg->docker_images))) { throw new BadRequestHttpException('This server\'s Docker image has been manually set by an administrator and cannot be updated.'); } diff --git a/app/Http/Requests/Api/Client/Servers/Settings/SetDockerImageRequest.php b/app/Http/Requests/Api/Client/Servers/Settings/SetDockerImageRequest.php index bd3a1e65fb..abe5d0436d 100644 --- a/app/Http/Requests/Api/Client/Servers/Settings/SetDockerImageRequest.php +++ b/app/Http/Requests/Api/Client/Servers/Settings/SetDockerImageRequest.php @@ -27,7 +27,7 @@ public function rules(): array Assert::isInstanceOf($server, Server::class); return [ - 'docker_image' => ['required', 'string', Rule::in($server->egg->docker_images)], + 'docker_image' => ['required', 'string', Rule::in(array_values($server->egg->docker_images))], ]; } } diff --git a/app/Models/Egg.php b/app/Models/Egg.php index 6e2c26802f..dbb90d301a 100644 --- a/app/Models/Egg.php +++ b/app/Models/Egg.php @@ -12,7 +12,7 @@ * @property array|null $features * @property string $docker_image -- deprecated, use $docker_images * @property string $update_url - * @property array $docker_images + * @property array $docker_images * @property array|null $file_denylist * @property string|null $config_files * @property string|null $config_startup @@ -50,6 +50,11 @@ class Egg extends Model */ public const RESOURCE_NAME = 'egg'; + /** + * Defines the current egg export version. + */ + public const EXPORT_VERSION = 'PTDL_v2'; + /** * Different features that can be enabled on any given egg. These are used internally * to determine which types of frontend functionality should be shown to the user. Eggs diff --git a/app/Services/Eggs/Sharing/EggExporterService.php b/app/Services/Eggs/Sharing/EggExporterService.php index f646561509..26723747a0 100644 --- a/app/Services/Eggs/Sharing/EggExporterService.php +++ b/app/Services/Eggs/Sharing/EggExporterService.php @@ -3,6 +3,7 @@ namespace Pterodactyl\Services\Eggs\Sharing; use Carbon\Carbon; +use Pterodactyl\Models\Egg; use Illuminate\Support\Collection; use Pterodactyl\Models\EggVariable; use Pterodactyl\Contracts\Repository\EggRepositoryInterface; @@ -34,7 +35,7 @@ public function handle(int $egg): string $struct = [ '_comment' => 'DO NOT EDIT: FILE GENERATED AUTOMATICALLY BY PTERODACTYL PANEL - PTERODACTYL.IO', 'meta' => [ - 'version' => 'PTDL_v1', + 'version' => Egg::EXPORT_VERSION, 'update_url' => $egg->update_url, ], 'exported_at' => Carbon::now()->toIso8601String(), @@ -42,7 +43,7 @@ public function handle(int $egg): string 'author' => $egg->author, 'description' => $egg->description, 'features' => $egg->features, - 'images' => $egg->docker_images, + 'docker_images' => $egg->docker_images, 'file_denylist' => Collection::make($egg->inherit_file_denylist)->filter(function ($value) { return !empty($value); }), @@ -63,6 +64,7 @@ public function handle(int $egg): string 'variables' => $egg->variables->transform(function (EggVariable $item) { return Collection::make($item->toArray()) ->except(['id', 'egg_id', 'created_at', 'updated_at']) + ->merge(['field_type' => 'text']) ->toArray(); }), ]; diff --git a/app/Services/Eggs/Sharing/EggImporterService.php b/app/Services/Eggs/Sharing/EggImporterService.php index 7292776904..74f71e1bdb 100644 --- a/app/Services/Eggs/Sharing/EggImporterService.php +++ b/app/Services/Eggs/Sharing/EggImporterService.php @@ -10,7 +10,6 @@ use Illuminate\Database\ConnectionInterface; use Pterodactyl\Contracts\Repository\EggRepositoryInterface; use Pterodactyl\Contracts\Repository\NestRepositoryInterface; -use Pterodactyl\Exceptions\Service\Egg\BadJsonFormatException; use Pterodactyl\Exceptions\Service\InvalidFileUploadException; use Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface; @@ -56,8 +55,8 @@ public function __construct( * * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - * @throws \Pterodactyl\Exceptions\Service\Egg\BadJsonFormatException * @throws \Pterodactyl\Exceptions\Service\InvalidFileUploadException + * @throws \JsonException */ public function handle(UploadedFile $file, int $nest): Egg { @@ -66,13 +65,13 @@ public function handle(UploadedFile $file, int $nest): Egg } /** @var array $parsed */ - $parsed = json_decode($file->openFile()->fread($file->getSize()), true); - if (json_last_error() !== 0) { - throw new BadJsonFormatException(trans('exceptions.nest.importer.json_error', ['error' => json_last_error_msg()])); + $parsed = json_decode($file->openFile()->fread($file->getSize()), true, 512, JSON_THROW_ON_ERROR); + if (!in_array(Arr::get($parsed, 'meta.version') ?? '', ['PTDL_v1', 'PTDL_v2'])) { + throw new InvalidFileUploadException(trans('exceptions.nest.importer.invalid_json_provided')); } - if (Arr::get($parsed, 'meta.version') !== 'PTDL_v1') { - throw new InvalidFileUploadException(trans('exceptions.nest.importer.invalid_json_provided')); + if ($parsed['meta']['version'] !== Egg::EXPORT_VERSION) { + $parsed = $this->convertV1ToV2($parsed); } $nest = $this->nestRepository->getWithEggs($nest); @@ -86,9 +85,7 @@ public function handle(UploadedFile $file, int $nest): Egg 'name' => Arr::get($parsed, 'name'), 'description' => Arr::get($parsed, 'description'), 'features' => Arr::get($parsed, 'features'), - // Maintain backwards compatability for eggs that are still using the old single image - // string format. New eggs can provide an array of Docker images that can be used. - 'docker_images' => Arr::get($parsed, 'images') ?? [Arr::get($parsed, 'image')], + 'docker_images' => Arr::get($parsed, 'docker_images'), 'file_denylist' => Collection::make(Arr::get($parsed, 'file_denylist'))->filter(function ($value) { return !empty($value); }), @@ -105,6 +102,8 @@ public function handle(UploadedFile $file, int $nest): Egg ], true, true); Collection::make($parsed['variables'] ?? [])->each(function (array $variable) use ($egg) { + unset($variable['field_type']); + $this->eggVariableRepository->create(array_merge($variable, [ 'egg_id' => $egg->id, ])); @@ -114,4 +113,33 @@ public function handle(UploadedFile $file, int $nest): Egg return $egg; } + + /** + * Converts a PTDL_V1 egg into the expected PTDL_V2 egg format. This just handles + * the "docker_images" field potentially not being present, and not being in the + * expected "key => value" format. + */ + protected function convertV1ToV2(array $parsed): array + { + // Maintain backwards compatability for eggs that are still using the old single image + // string format. New eggs can provide an array of Docker images that can be used. + if (!isset($parsed['images'])) { + $images = [Arr::get($parsed, 'image') ?? 'nil']; + } else { + $images = $parsed['images']; + } + + unset($parsed['images'], $parsed['image']); + + $parsed['docker_images'] = []; + foreach ($images as $image) { + $parsed['docker_images'][$image] = $image; + } + + $parsed['variables'] = array_map(function ($value) { + return array_merge($value, ['field_type' => 'text']); + }, $parsed['variables']); + + return $parsed; + } } diff --git a/app/Transformers/Api/Application/EggTransformer.php b/app/Transformers/Api/Application/EggTransformer.php index 8dccb7552b..d2b269b73c 100644 --- a/app/Transformers/Api/Application/EggTransformer.php +++ b/app/Transformers/Api/Application/EggTransformer.php @@ -3,6 +3,7 @@ namespace Pterodactyl\Transformers\Api\Application; use Pterodactyl\Models\Egg; +use Illuminate\Support\Arr; use Pterodactyl\Models\Nest; use Pterodactyl\Models\Server; use Pterodactyl\Models\EggVariable; @@ -49,7 +50,7 @@ public function transform(Egg $model) // "docker_image" is deprecated, but left here to avoid breaking too many things at once // in external software. We'll remove it down the road once things have gotten the chance // to upgrade to using "docker_images". - 'docker_image' => count($model->docker_images) > 0 ? $model->docker_images[0] : '', + 'docker_image' => count($model->docker_images) > 0 ? Arr::first($model->docker_images) : '', 'docker_images' => $model->docker_images, 'config' => [ 'files' => json_decode($model->config_files, true), diff --git a/database/Seeders/eggs/minecraft/egg-bungeecord.json b/database/Seeders/eggs/minecraft/egg-bungeecord.json index ac3749c8c9..cc7440823c 100644 --- a/database/Seeders/eggs/minecraft/egg-bungeecord.json +++ b/database/Seeders/eggs/minecraft/egg-bungeecord.json @@ -1,10 +1,10 @@ { "_comment": "DO NOT EDIT: FILE GENERATED AUTOMATICALLY BY PTERODACTYL PANEL - PTERODACTYL.IO", "meta": { - "version": "PTDL_v1", + "version": "PTDL_v2", "update_url": null }, - "exported_at": "2021-11-14T19:23:12+00:00", + "exported_at": "2022-05-07T17:35:07-04:00", "name": "Bungeecord", "author": "support@pterodactyl.io", "description": "For a long time, Minecraft server owners have had a dream that encompasses a free, easy, and reliable way to connect multiple Minecraft servers together. BungeeCord is the answer to said dream. Whether you are a small server wishing to string multiple game-modes together, or the owner of the ShotBow Network, BungeeCord is the ideal solution for you. With the help of BungeeCord, you will be able to unlock your community's full potential.", @@ -13,12 +13,12 @@ "java_version", "pid_limit" ], - "images": [ - "ghcr.io\/pterodactyl\/yolks:java_8", - "ghcr.io\/pterodactyl\/yolks:java_11", - "ghcr.io\/pterodactyl\/yolks:java_16", - "ghcr.io\/pterodactyl\/yolks:java_17" - ], + "docker_images": { + "Java 17": "ghcr.io\/pterodactyl\/yolks:java_17", + "Java 16": "ghcr.io\/pterodactyl\/yolks:java_16", + "Java 11": "ghcr.io\/pterodactyl\/yolks:java_11", + "Java 8": "ghcr.io\/pterodactyl\/yolks:java_8" + }, "file_denylist": [], "startup": "java -Xms128M -Xmx{{SERVER_MEMORY}}M -jar {{SERVER_JARFILE}}", "config": { @@ -42,7 +42,8 @@ "default_value": "latest", "user_viewable": true, "user_editable": true, - "rules": "required|alpha_num|between:1,6" + "rules": "required|alpha_num|between:1,6", + "field_type": "text" }, { "name": "Bungeecord Jar File", @@ -51,7 +52,8 @@ "default_value": "bungeecord.jar", "user_viewable": true, "user_editable": true, - "rules": "required|regex:\/^([\\w\\d._-]+)(\\.jar)$\/" + "rules": "required|regex:\/^([\\w\\d._-]+)(\\.jar)$\/", + "field_type": "text" } ] -} +} \ No newline at end of file diff --git a/database/Seeders/eggs/minecraft/egg-forge-minecraft.json b/database/Seeders/eggs/minecraft/egg-forge-minecraft.json index a077df6d15..ae105110d4 100644 --- a/database/Seeders/eggs/minecraft/egg-forge-minecraft.json +++ b/database/Seeders/eggs/minecraft/egg-forge-minecraft.json @@ -1,10 +1,10 @@ { "_comment": "DO NOT EDIT: FILE GENERATED AUTOMATICALLY BY PTERODACTYL PANEL - PTERODACTYL.IO", "meta": { - "version": "PTDL_v1", + "version": "PTDL_v2", "update_url": null }, - "exported_at": "2021-12-11T22:51:29+00:00", + "exported_at": "2022-05-07T17:35:08-04: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.", @@ -13,12 +13,12 @@ "java_version", "pid_limit" ], - "images": [ - "ghcr.io\/pterodactyl\/yolks:java_17", - "ghcr.io\/pterodactyl\/yolks:java_16", - "ghcr.io\/pterodactyl\/yolks:java_11", - "ghcr.io\/pterodactyl\/yolks:java_8" - ], + "docker_images": { + "Java 17": "ghcr.io\/pterodactyl\/yolks:java_17", + "Java 16": "ghcr.io\/pterodactyl\/yolks:java_16", + "Java 11": "ghcr.io\/pterodactyl\/yolks:java_11", + "Java 8": "ghcr.io\/pterodactyl\/yolks:java_8" + }, "file_denylist": [], "startup": "java -Xms128M -Xmx{{SERVER_MEMORY}}M -Dterminal.jline=false -Dterminal.ansi=true $( [[ ! -f unix_args.txt ]] && printf %s \"-jar {{SERVER_JARFILE}}\" || printf %s \"@unix_args.txt\" )", "config": { @@ -42,7 +42,8 @@ "default_value": "server.jar", "user_viewable": true, "user_editable": true, - "rules": "required|regex:\/^([\\w\\d._-]+)(\\.jar)$\/" + "rules": "required|regex:\/^([\\w\\d._-]+)(\\.jar)$\/", + "field_type": "text" }, { "name": "Minecraft Version", @@ -51,7 +52,8 @@ "default_value": "latest", "user_viewable": true, "user_editable": true, - "rules": "required|string|max:9" + "rules": "required|string|max:9", + "field_type": "text" }, { "name": "Build Type", @@ -60,7 +62,8 @@ "default_value": "recommended", "user_viewable": true, "user_editable": true, - "rules": "required|string|in:recommended,latest" + "rules": "required|string|in:recommended,latest", + "field_type": "text" }, { "name": "Forge Version", @@ -69,7 +72,8 @@ "default_value": "", "user_viewable": true, "user_editable": true, - "rules": "nullable|string|max:25" + "rules": "nullable|string|max:25", + "field_type": "text" } ] } \ No newline at end of file diff --git a/database/Seeders/eggs/minecraft/egg-paper.json b/database/Seeders/eggs/minecraft/egg-paper.json index 39ab216659..d6db44fd58 100644 --- a/database/Seeders/eggs/minecraft/egg-paper.json +++ b/database/Seeders/eggs/minecraft/egg-paper.json @@ -1,10 +1,10 @@ { "_comment": "DO NOT EDIT: FILE GENERATED AUTOMATICALLY BY PTERODACTYL PANEL - PTERODACTYL.IO", "meta": { - "version": "PTDL_v1", + "version": "PTDL_v2", "update_url": null }, - "exported_at": "2022-03-11T10:21:01-05:00", + "exported_at": "2022-05-07T17:35:09-04:00", "name": "Paper", "author": "parker@pterodactyl.io", "description": "High performance Spigot fork that aims to fix gameplay and mechanics inconsistencies.", @@ -13,12 +13,12 @@ "java_version", "pid_limit" ], - "images": [ - "ghcr.io\/pterodactyl\/yolks:java_8", - "ghcr.io\/pterodactyl\/yolks:java_11", - "ghcr.io\/pterodactyl\/yolks:java_16", - "ghcr.io\/pterodactyl\/yolks:java_17" - ], + "docker_images": { + "Java 17": "ghcr.io\/pterodactyl\/yolks:java_17", + "Java 16": "ghcr.io\/pterodactyl\/yolks:java_16", + "Java 11": "ghcr.io\/pterodactyl\/yolks:java_11", + "Java 8": "ghcr.io\/pterodactyl\/yolks:java_8" + }, "file_denylist": [], "startup": "java -Xms128M -Xmx{{SERVER_MEMORY}}M -Dterminal.jline=false -Dterminal.ansi=true -jar {{SERVER_JARFILE}}", "config": { @@ -42,7 +42,8 @@ "default_value": "latest", "user_viewable": true, "user_editable": true, - "rules": "nullable|string|max:20" + "rules": "nullable|string|max:20", + "field_type": "text" }, { "name": "Server Jar File", @@ -51,7 +52,8 @@ "default_value": "server.jar", "user_viewable": true, "user_editable": true, - "rules": "required|regex:\/^([\\w\\d._-]+)(\\.jar)$\/" + "rules": "required|regex:\/^([\\w\\d._-]+)(\\.jar)$\/", + "field_type": "text" }, { "name": "Download Path", @@ -60,7 +62,8 @@ "default_value": "", "user_viewable": false, "user_editable": false, - "rules": "nullable|string" + "rules": "nullable|string", + "field_type": "text" }, { "name": "Build Number", @@ -69,7 +72,8 @@ "default_value": "latest", "user_viewable": true, "user_editable": true, - "rules": "required|string|max:20" + "rules": "required|string|max:20", + "field_type": "text" } ] -} +} \ No newline at end of file diff --git a/database/Seeders/eggs/minecraft/egg-sponge--sponge-vanilla.json b/database/Seeders/eggs/minecraft/egg-sponge--sponge-vanilla.json index 908c1f4e55..70b247d2e5 100644 --- a/database/Seeders/eggs/minecraft/egg-sponge--sponge-vanilla.json +++ b/database/Seeders/eggs/minecraft/egg-sponge--sponge-vanilla.json @@ -1,10 +1,10 @@ { "_comment": "DO NOT EDIT: FILE GENERATED AUTOMATICALLY BY PTERODACTYL PANEL - PTERODACTYL.IO", "meta": { - "version": "PTDL_v1", + "version": "PTDL_v2", "update_url": null }, - "exported_at": "2021-10-22T19:19:17+02:00", + "exported_at": "2022-05-07T17:35:10-04:00", "name": "Sponge (SpongeVanilla)", "author": "support@pterodactyl.io", "description": "SpongeVanilla is the SpongeAPI implementation for Vanilla Minecraft.", @@ -13,11 +13,11 @@ "java_version", "pid_limit" ], - "images": [ - "ghcr.io\/pterodactyl\/yolks:java_8", - "ghcr.io\/pterodactyl\/yolks:java_11", - "ghcr.io\/pterodactyl\/yolks:java_16" - ], + "docker_images": { + "Java 16": "ghcr.io\/pterodactyl\/yolks:java_16", + "Java 11": "ghcr.io\/pterodactyl\/yolks:java_11", + "Java 8": "ghcr.io\/pterodactyl\/yolks:java_8" + }, "file_denylist": [], "startup": "java -Xms128M -Xmx{{SERVER_MEMORY}}M -jar {{SERVER_JARFILE}}", "config": { @@ -41,7 +41,8 @@ "default_value": "1.12.2-7.3.0", "user_viewable": true, "user_editable": true, - "rules": "required|regex:\/^([a-zA-Z0-9.\\-_]+)$\/" + "rules": "required|regex:\/^([a-zA-Z0-9.\\-_]+)$\/", + "field_type": "text" }, { "name": "Server Jar File", @@ -50,7 +51,8 @@ "default_value": "server.jar", "user_viewable": true, "user_editable": true, - "rules": "required|regex:\/^([\\w\\d._-]+)(\\.jar)$\/" + "rules": "required|regex:\/^([\\w\\d._-]+)(\\.jar)$\/", + "field_type": "text" } ] -} +} \ No newline at end of file diff --git a/database/Seeders/eggs/minecraft/egg-vanilla-minecraft.json b/database/Seeders/eggs/minecraft/egg-vanilla-minecraft.json index 2361a29746..9965c3b2cd 100644 --- a/database/Seeders/eggs/minecraft/egg-vanilla-minecraft.json +++ b/database/Seeders/eggs/minecraft/egg-vanilla-minecraft.json @@ -1,10 +1,10 @@ { "_comment": "DO NOT EDIT: FILE GENERATED AUTOMATICALLY BY PTERODACTYL PANEL - PTERODACTYL.IO", "meta": { - "version": "PTDL_v1", + "version": "PTDL_v2", "update_url": null }, - "exported_at": "2021-11-14T19:18:30+00:00", + "exported_at": "2022-05-07T17:35:11-04:00", "name": "Vanilla Minecraft", "author": "support@pterodactyl.io", "description": "Minecraft is a game about placing blocks and going on adventures. Explore randomly generated worlds and build amazing things from the simplest of homes to the grandest of castles. Play in Creative Mode with unlimited resources or mine deep in Survival Mode, crafting weapons and armor to fend off dangerous mobs. Do all this alone or with friends.", @@ -13,12 +13,12 @@ "java_version", "pid_limit" ], - "images": [ - "ghcr.io\/pterodactyl\/yolks:java_8", - "ghcr.io\/pterodactyl\/yolks:java_11", - "ghcr.io\/pterodactyl\/yolks:java_16", - "ghcr.io\/pterodactyl\/yolks:java_17" - ], + "docker_images": { + "Java 17": "ghcr.io\/pterodactyl\/yolks:java_17", + "Java 16": "ghcr.io\/pterodactyl\/yolks:java_16", + "Java 11": "ghcr.io\/pterodactyl\/yolks:java_11", + "Java 8": "ghcr.io\/pterodactyl\/yolks:java_8" + }, "file_denylist": [], "startup": "java -Xms128M -Xmx{{SERVER_MEMORY}}M -jar {{SERVER_JARFILE}}", "config": { @@ -42,7 +42,8 @@ "default_value": "server.jar", "user_viewable": true, "user_editable": true, - "rules": "required|regex:\/^([\\w\\d._-]+)(\\.jar)$\/" + "rules": "required|regex:\/^([\\w\\d._-]+)(\\.jar)$\/", + "field_type": "text" }, { "name": "Server Version", @@ -51,7 +52,8 @@ "default_value": "latest", "user_viewable": true, "user_editable": true, - "rules": "required|string|between:3,15" + "rules": "required|string|between:3,15", + "field_type": "text" } ] -} +} \ No newline at end of file diff --git a/database/migrations/2022_05_07_165334_migrate_egg_images_array_to_new_format.php b/database/migrations/2022_05_07_165334_migrate_egg_images_array_to_new_format.php new file mode 100644 index 0000000000..78dfe6e37a --- /dev/null +++ b/database/migrations/2022_05_07_165334_migrate_egg_images_array_to_new_format.php @@ -0,0 +1,40 @@ + value pairings to support naming the + * images provided. + */ + public function up() + { + DB::table('eggs')->select(['id', 'docker_images'])->cursor()->each(function ($egg) { + $images = is_null($egg->docker_images) ? [] : json_decode($egg->docker_images, true, 512, JSON_THROW_ON_ERROR); + + $results = []; + foreach ($images as $key => $value) { + $results[is_int($key) ? $value : $key] = $value; + } + + DB::table('eggs')->where('id', $egg->id)->update(['docker_images' => $results]); + }); + } + + /** + * Reverse the migrations. This just keeps the values from the docker images array. + * + * @return void + */ + public function down() + { + DB::table('eggs')->select(['id', 'docker_images'])->cursor()->each(function ($egg) { + DB::table('eggs')->where('id', $egg->id)->update([ + 'docker_images' => array_values(json_decode($egg->docker_images, true, 512, JSON_THROW_ON_ERROR)), + ]); + }); + } +} diff --git a/resources/scripts/api/swr/getServerStartup.ts b/resources/scripts/api/swr/getServerStartup.ts index b7089b7bd1..0755be211c 100644 --- a/resources/scripts/api/swr/getServerStartup.ts +++ b/resources/scripts/api/swr/getServerStartup.ts @@ -6,7 +6,7 @@ import { ServerEggVariable } from '@/api/server/types'; interface Response { invocation: string; variables: ServerEggVariable[]; - dockerImages: string[]; + dockerImages: Record; } export default (uuid: string, initialData?: Response) => useSWR([ uuid, '/startup' ], async (): Promise => { @@ -14,5 +14,9 @@ export default (uuid: string, initialData?: Response) => useSWR([ uuid, '/startu const variables = ((data as FractalResponseList).data || []).map(rawDataToServerEggVariable); - return { invocation: data.meta.startup_command, variables, dockerImages: data.meta.docker_images || [] }; + return { + variables, + invocation: data.meta.startup_command, + dockerImages: data.meta.docker_images || {}, + }; }, { initialData, errorRetryCount: 3 }); diff --git a/resources/scripts/components/server/startup/StartupContainer.tsx b/resources/scripts/components/server/startup/StartupContainer.tsx index b5bb1fea5d..3737c54f3c 100644 --- a/resources/scripts/components/server/startup/StartupContainer.tsx +++ b/resources/scripts/components/server/startup/StartupContainer.tsx @@ -29,11 +29,11 @@ const StartupContainer = () => { const { data, error, isValidating, mutate } = getServerStartup(uuid, { ...variables, - dockerImages: [ variables.dockerImage ], + dockerImages: { [variables.dockerImage]: variables.dockerImage }, }); const setServerFromState = ServerContext.useStoreActions(actions => actions.server.setServerFromState); - const isCustomImage = data && !data.dockerImages.map(v => v.toLowerCase()).includes(variables.dockerImage.toLowerCase()); + const isCustomImage = data && !Object.values(data.dockerImages).map(v => v.toLowerCase()).includes(variables.dockerImage.toLowerCase()); useEffect(() => { // Since we're passing in initial data this will not trigger on mount automatically. We @@ -87,16 +87,18 @@ const StartupContainer = () => { - {data.dockerImages.length > 1 && !isCustomImage ? + {Object.keys(data.dockerImages).length > 1 && !isCustomImage ? <> diff --git a/resources/views/admin/eggs/view.blade.php b/resources/views/admin/eggs/view.blade.php index 7a235e069c..a2cda15e88 100644 --- a/resources/views/admin/eggs/view.blade.php +++ b/resources/views/admin/eggs/view.blade.php @@ -83,8 +83,13 @@
- -

The docker images available to servers using this egg. Enter one per line. Users will be able to select from this list of images if more than one value is provided.

+ +

+ The docker images available to servers using this egg. Enter one per line. Users + will be able to select from this list of images if more than one value is provided. + Optionally, a display name may be provided by prefixing the image with the name + followed by a pipe character, and then the image URL. Example: Display Name|ghcr.io/my/egg +

diff --git a/tests/Unit/Services/Api/KeyCreationServiceTest.php b/tests/Unit/Services/Api/KeyCreationServiceTest.php deleted file mode 100644 index 5c847f1448..0000000000 --- a/tests/Unit/Services/Api/KeyCreationServiceTest.php +++ /dev/null @@ -1,167 +0,0 @@ -encrypter = m::mock(Encrypter::class); - $this->repository = m::mock(ApiKeyRepositoryInterface::class); - } - - /** - * Test that the service is able to create a keypair and assign the correct permissions. - */ - public function testKeyIsCreated() - { - $model = ApiKey::factory()->make(); - - $this->getFunctionMock('\\Pterodactyl\\Services\\Api', 'str_random') - ->expects($this->exactly(2))->willReturnCallback(function ($length) { - return 'str_' . $length; - }); - - $this->encrypter->shouldReceive('encrypt')->with('str_' . ApiKey::KEY_LENGTH)->once()->andReturn($model->token); - - $this->repository->shouldReceive('create')->with([ - 'test-data' => 'test', - 'key_type' => ApiKey::TYPE_NONE, - 'identifier' => 'str_' . ApiKey::IDENTIFIER_LENGTH, - 'token' => $model->token, - ], true, true)->once()->andReturn($model); - - $response = $this->getService()->handle(['test-data' => 'test']); - - $this->assertNotEmpty($response); - $this->assertInstanceOf(ApiKey::class, $response); - $this->assertSame($model, $response); - } - - /** - * Test that an identifier is only set by the function. - */ - public function testIdentifierAndTokenAreOnlySetByFunction() - { - $model = ApiKey::factory()->make(); - - $this->getFunctionMock('\\Pterodactyl\\Services\\Api', 'str_random') - ->expects($this->exactly(2))->willReturnCallback(function ($length) { - return 'str_' . $length; - }); - - $this->encrypter->shouldReceive('encrypt')->with('str_' . ApiKey::KEY_LENGTH)->once()->andReturn($model->token); - - $this->repository->shouldReceive('create')->with([ - 'key_type' => ApiKey::TYPE_NONE, - 'identifier' => 'str_' . ApiKey::IDENTIFIER_LENGTH, - 'token' => $model->token, - ], true, true)->once()->andReturn($model); - - $response = $this->getService()->handle(['identifier' => 'customIdentifier', 'token' => 'customToken']); - - $this->assertNotEmpty($response); - $this->assertInstanceOf(ApiKey::class, $response); - $this->assertSame($model, $response); - } - - /** - * Test that permissions passed in are loaded onto the key data. - */ - public function testPermissionsAreRetrievedForApplicationKeys() - { - $model = ApiKey::factory()->make(); - - $this->getFunctionMock('\\Pterodactyl\\Services\\Api', 'str_random') - ->expects($this->exactly(2))->willReturnCallback(function ($length) { - return 'str_' . $length; - }); - - $this->encrypter->shouldReceive('encrypt')->with('str_' . ApiKey::KEY_LENGTH)->once()->andReturn($model->token); - - $this->repository->shouldReceive('create')->with([ - 'key_type' => ApiKey::TYPE_APPLICATION, - 'identifier' => 'str_' . ApiKey::IDENTIFIER_LENGTH, - 'token' => $model->token, - 'permission-key' => 'exists', - ], true, true)->once()->andReturn($model); - - $response = $this->getService()->setKeyType(ApiKey::TYPE_APPLICATION)->handle([], ['permission-key' => 'exists']); - - $this->assertNotEmpty($response); - $this->assertInstanceOf(ApiKey::class, $response); - $this->assertSame($model, $response); - } - - /** - * Test that permissions are not retrieved for any key that is not an application key. - * - * @dataProvider keyTypeDataProvider - */ - public function testPermissionsAreNotRetrievedForNonApplicationKeys($keyType) - { - $model = ApiKey::factory()->make(); - - $this->getFunctionMock('\\Pterodactyl\\Services\\Api', 'str_random') - ->expects($this->exactly(2))->willReturnCallback(function ($length) { - return 'str_' . $length; - }); - - $this->encrypter->shouldReceive('encrypt')->with('str_' . ApiKey::KEY_LENGTH)->once()->andReturn($model->token); - - $this->repository->shouldReceive('create')->with([ - 'key_type' => $keyType, - 'identifier' => 'str_' . ApiKey::IDENTIFIER_LENGTH, - 'token' => $model->token, - ], true, true)->once()->andReturn($model); - - $response = $this->getService()->setKeyType($keyType)->handle([], ['fake-permission' => 'should-not-exist']); - - $this->assertNotEmpty($response); - $this->assertInstanceOf(ApiKey::class, $response); - $this->assertSame($model, $response); - } - - /** - * Provide key types that are not an application specific key. - */ - public function keyTypeDataProvider(): array - { - return [ - [ApiKey::TYPE_NONE], [ApiKey::TYPE_ACCOUNT], [ApiKey::TYPE_DAEMON_USER], [ApiKey::TYPE_DAEMON_APPLICATION], - ]; - } - - /** - * Return an instance of the service with mocked dependencies for testing. - */ - private function getService(): KeyCreationService - { - return new KeyCreationService($this->repository, $this->encrypter); - } -} From 6d5ca5a811537420965935e5f9e46b4850473208 Mon Sep 17 00:00:00 2001 From: DaneEveritt Date: Sat, 7 May 2022 18:18:14 -0400 Subject: [PATCH 017/458] Update java version modal to only suggest allowed images --- resources/scripts/api/swr/getServerStartup.ts | 6 +- .../features/JavaVersionModalFeature.tsx | 113 ++++++++++-------- 2 files changed, 63 insertions(+), 56 deletions(-) diff --git a/resources/scripts/api/swr/getServerStartup.ts b/resources/scripts/api/swr/getServerStartup.ts index 0755be211c..94a4faf550 100644 --- a/resources/scripts/api/swr/getServerStartup.ts +++ b/resources/scripts/api/swr/getServerStartup.ts @@ -1,4 +1,4 @@ -import useSWR from 'swr'; +import useSWR, { ConfigInterface } from 'swr'; import http, { FractalResponseList } from '@/api/http'; import { rawDataToServerEggVariable } from '@/api/transformers'; import { ServerEggVariable } from '@/api/server/types'; @@ -9,7 +9,7 @@ interface Response { dockerImages: Record; } -export default (uuid: string, initialData?: Response) => useSWR([ uuid, '/startup' ], async (): Promise => { +export default (uuid: string, initialData?: Response | null, config?: ConfigInterface) => useSWR([ uuid, '/startup' ], async (): Promise => { const { data } = await http.get(`/api/client/servers/${uuid}/startup`); const variables = ((data as FractalResponseList).data || []).map(rawDataToServerEggVariable); @@ -19,4 +19,4 @@ export default (uuid: string, initialData?: Response) => useSWR([ uuid, '/startu invocation: data.meta.startup_command, dockerImages: data.meta.docker_images || {}, }; -}, { initialData, errorRetryCount: 3 }); +}, { initialData: initialData || undefined, errorRetryCount: 3, ...(config || {}) }); diff --git a/resources/scripts/components/server/features/JavaVersionModalFeature.tsx b/resources/scripts/components/server/features/JavaVersionModalFeature.tsx index efae064c57..58c4e614e2 100644 --- a/resources/scripts/components/server/features/JavaVersionModalFeature.tsx +++ b/resources/scripts/components/server/features/JavaVersionModalFeature.tsx @@ -8,47 +8,46 @@ import FlashMessageRender from '@/components/FlashMessageRender'; import useFlash from '@/plugins/useFlash'; import { SocketEvent, SocketRequest } from '@/components/server/events'; import Select from '@/components/elements/Select'; +import useWebsocketEvent from '@/plugins/useWebsocketEvent'; +import Can from '@/components/elements/Can'; +import getServerStartup from '@/api/swr/getServerStartup'; +import InputSpinner from '@/components/elements/InputSpinner'; -const dockerImageList = [ - { name: 'Java 17', image: 'ghcr.io/pterodactyl/yolks:java_17' }, - { name: 'Java 16', image: 'ghcr.io/pterodactyl/yolks:java_16' }, - { name: 'Java 11', image: 'ghcr.io/pterodactyl/yolks:java_11' }, - { name: 'Java 8', image: 'ghcr.io/pterodactyl/yolks:java_8' }, +const MATCH_ERRORS = [ + 'minecraft 1.17 requires running the server with java 16 or above', + 'minecraft 1.18 requires running the server with java 17 or above', + 'java.lang.unsupportedclassversionerror', + 'unsupported major.minor version', + 'has been compiled by a more recent version of the java runtime', ]; const JavaVersionModalFeature = () => { const [ visible, setVisible ] = useState(false); const [ loading, setLoading ] = useState(false); - const [ selectedVersion, setSelectedVersion ] = useState('ghcr.io/pterodactyl/yolks:java_17'); + const [ selectedVersion, setSelectedVersion ] = useState(''); const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); const status = ServerContext.useStoreState(state => state.status.value); const { clearFlashes, clearAndAddHttpError } = useFlash(); - const { connected, instance } = ServerContext.useStoreState(state => state.socket); + const { instance } = ServerContext.useStoreState(state => state.socket); - useEffect(() => { - if (!connected || !instance || status === 'running') return; + const { data, isValidating, mutate } = getServerStartup(uuid, null, { revalidateOnMount: false }); - const errors = [ - 'minecraft 1.17 requires running the server with java 16 or above', - 'minecraft 1.18 requires running the server with java 17 or above', - 'java.lang.unsupportedclassversionerror', - 'unsupported major.minor version', - 'has been compiled by a more recent version of the java runtime', - ]; + useEffect(() => { + if (!visible) return; - const listener = (line: string) => { - if (errors.some(p => line.toLowerCase().includes(p))) { - setVisible(true); - } - }; + mutate().then((value) => { + setSelectedVersion(Object.keys(value?.dockerImages || [])[0] || ''); + }); + }, [ visible ]); - instance.addListener(SocketEvent.CONSOLE_OUTPUT, listener); + useWebsocketEvent(SocketEvent.CONSOLE_OUTPUT, (data) => { + if (status === 'running') return; - return () => { - instance.removeListener(SocketEvent.CONSOLE_OUTPUT, listener); - }; - }, [ connected, instance, status ]); + if (MATCH_ERRORS.some(p => data.toLowerCase().includes(p.toLowerCase()))) { + setVisible(true); + } + }); const updateJava = () => { setLoading(true); @@ -59,14 +58,9 @@ const JavaVersionModalFeature = () => { if (status === 'offline' && instance) { instance.send(SocketRequest.SET_STATE, 'restart'); } - - setLoading(false); setVisible(false); }) - .catch(error => { - console.error(error); - clearAndAddHttpError({ key: 'feature:javaVersion', error }); - }) + .catch(error => clearAndAddHttpError({ key: 'feature:javaVersion', error })) .then(() => setLoading(false)); }; @@ -75,30 +69,43 @@ const JavaVersionModalFeature = () => { }, []); return ( - setVisible(false)} closeOnBackground={false} showSpinnerOverlay={loading}> + setVisible(false)} + closeOnBackground={false} + showSpinnerOverlay={loading} + > -

Invalid Java version, update Docker image?

-

This server is unable to start due to the required Java version not being met.

-

By pressing {'"Update Docker Image"'} below you are acknowledging that the Docker image this server uses will be changed to an image below that has the Java version you are requesting.

-
-

Please select a Java version from the list below.

- -
-
- - + +
); From a6df0afefdbaaff1596fd6a4c11e5c0ab43bc817 Mon Sep 17 00:00:00 2001 From: DaneEveritt Date: Sat, 7 May 2022 18:30:12 -0400 Subject: [PATCH 018/458] Update CHANGELOG.md --- CHANGELOG.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 78a4034494..08ad949ab9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,36 @@ 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.8.0 +**Important:** this version updates the `version` field on generated Eggs to be `PTDL_v2` due to formatting changes. This +should be completely seamless for most installations as the Panel is able to convert between the two. Custom solutions +using these eggs should be updated to account for the new format. + +### Fixed +* Schedules are no longer run when a server is suspended or marked as installing. +* The remote field when creating a database is no longer limited to an IP address and `%` wildcard — all expected MySQL remote host values are allowed. +* Allocations cannot be deleted from a server by a user if the server is configured with an `allocation_limit` set to `0`. +* The Java Version modal no longer shows a dropdown and update option to users that do not have permission to make those changes. +* The Java Version modal now correctly returns only the images available to the server's selected Egg. + +### Changed +* Forces HTTPS on URLs when the `APP_URL` value is set and includes `https://` within the URL. This addresses proxy misconfiguration issues that would cause URLs to be generated incorrectly. +* Lowers the default timeout values for requests to Wings instances from 10 seconds to 5 seconds. +* Additional permissions (`CREATE TEMPORARY TABLES`, `CREATE VIEW`, `SHOW VIEW`, `EVENT`, and `TRIGGER`) are granted to users when creating new databases for servers. +* development: removed Laravel Debugbar in favor of Clockwork for debugging. +* The 2FA input field when logging in is now correctly identified as `one-time-password` to help browser autofill capabilities. + +### Added +* Added support for PHP 8.1 in addition to PHP 8.0 and 7.4. +* Adds more support for catching potential PID exhaustion errors in different games. +* It is now possible to create a new node on the Panel using an artisan command. +* A new cron cheatsheet has been added which appears when creating a schedule. +* Adds support for filtering the `/api/application/nodes/:id/allocations` endpoint using `?filter[server_id]=0` to only return allocations that are not currently assigned to a server on that node. +* Adds support for naming docker image values in an Egg to improve front-end display capabilities. + +### Removed +* Removes Google Analytics from the front end code. + ## v1.7.0 ### Fixed * Fixes typo in message shown to user when deleting a database. From 44bb8b4abf7ab1b94e3a422c6ccd0de45bf337d5 Mon Sep 17 00:00:00 2001 From: DaneEveritt Date: Sat, 7 May 2022 18:33:41 -0400 Subject: [PATCH 019/458] Fix bad config for PHP 8.1 --- config/trustedproxy.php | 28 +--------------------------- 1 file changed, 1 insertion(+), 27 deletions(-) diff --git a/config/trustedproxy.php b/config/trustedproxy.php index d14bf4a1dd..7e0166af8e 100644 --- a/config/trustedproxy.php +++ b/config/trustedproxy.php @@ -24,31 +24,5 @@ * subsequently passed through. */ 'proxies' => in_array(env('TRUSTED_PROXIES', []), ['*', '**']) ? - env('TRUSTED_PROXIES') : explode(',', env('TRUSTED_PROXIES', null)), - - /* - * Or, to trust all proxies that connect - * directly to your server, uncomment this: - */ - // 'proxies' => '*', - - /* - * Or, to trust ALL proxies, including those that - * are in a chain of forwarding, uncomment this: - */ - // 'proxies' => '**', - - /* - * Default Header Names - * - * Change these if the proxy does - * not send the default header names. - * - * Note that headers such as X-Forwarded-For - * are transformed to HTTP_X_FORWARDED_FOR format. - * - * The following are Symfony defaults, found in - * \Symfony\Component\HttpFoundation\Request::$trustedHeaders - */ - 'headers' => \Illuminate\Http\Request::HEADER_X_FORWARDED_ALL, + env('TRUSTED_PROXIES') : explode(',', env('TRUSTED_PROXIES') ?? ''), ]; From 0e3e14aa936cac526ad9a420c00af8ca89606134 Mon Sep 17 00:00:00 2001 From: Alex Date: Sun, 8 May 2022 18:28:17 +0300 Subject: [PATCH 020/458] fix: artisan translations (#4069) --- resources/lang/en/command/messages.php | 97 ++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 resources/lang/en/command/messages.php diff --git a/resources/lang/en/command/messages.php b/resources/lang/en/command/messages.php new file mode 100644 index 0000000000..b02fa8c298 --- /dev/null +++ b/resources/lang/en/command/messages.php @@ -0,0 +1,97 @@ + [ + 'no_location_found' => 'Could not locate a record matching the provided short code.', + 'ask_short' => 'Location Short Code', + 'ask_long' => 'Location Description', + 'created' => 'Successfully created a new location (:name) with an ID of :id.', + 'deleted' => 'Successfully deleted the requested location.', + ], + 'user' => [ + 'search_users' => 'Enter a Username, User ID, or Email Address', + 'select_search_user' => 'ID of user to delete (Enter \'0\' to re-search)', + 'deleted' => 'User successfully deleted from the Panel.', + 'confirm_delete' => 'Are you sure you want to delete this user from the Panel?', + 'no_users_found' => 'No users were found for the search term provided.', + 'multiple_found' => 'Multiple accounts were found for the user provided, unable to delete a user because of the --no-interaction flag.', + 'ask_admin' => 'Is this user an administrator?', + 'ask_email' => 'Email Address', + 'ask_username' => 'Username', + 'ask_name_first' => 'First Name', + 'ask_name_last' => 'Last Name', + 'ask_password' => 'Password', + 'ask_password_tip' => 'If you would like to create an account with a random password emailed to the user, re-run this command (CTRL+C) and pass the `--no-password` flag.', + 'ask_password_help' => 'Passwords must be at least 8 characters in length and contain at least one capital letter and number.', + '2fa_help_text' => [ + 'This command will disable 2-factor authentication for a user\'s account if it is enabled. This should only be used as an account recovery command if the user is locked out of their account.', + 'If this is not what you wanted to do, press CTRL+C to exit this process.', + ], + '2fa_disabled' => '2-Factor authentication has been disabled for :email.', + ], + 'schedule' => [ + 'output_line' => 'Dispatching job for first task in `:schedule` (:hash).', + ], + 'maintenance' => [ + 'deleting_service_backup' => 'Deleting service backup file :file.', + ], + 'server' => [ + 'rebuild_failed' => 'Rebuild request for ":name" (#:id) on node ":node" failed with error: :message', + 'reinstall' => [ + 'failed' => 'Reinstall request for ":name" (#:id) on node ":node" failed with error: :message', + 'confirm' => 'You are about to reinstall against a group of servers. Do you wish to continue?', + ], + 'power' => [ + 'confirm' => 'You are about to perform a :action against :count servers. Do you wish to continue?', + 'action_failed' => 'Power action request for ":name" (#:id) on node ":node" failed with error: :message', + ], + ], + 'environment' => [ + 'mail' => [ + 'ask_smtp_host' => 'SMTP Host (e.g. smtp.gmail.com)', + 'ask_smtp_port' => 'SMTP Port', + 'ask_smtp_username' => 'SMTP Username', + 'ask_smtp_password' => 'SMTP Password', + 'ask_mailgun_domain' => 'Mailgun Domain', + 'ask_mailgun_endpoint' => 'Mailgun Endpoint', + 'ask_mailgun_secret' => 'Mailgun Secret', + 'ask_mandrill_secret' => 'Mandrill Secret', + 'ask_postmark_username' => 'Postmark API Key', + 'ask_driver' => 'Which driver should be used for sending emails?', + 'ask_mail_from' => 'Email address emails should originate from', + 'ask_mail_name' => 'Name that emails should appear from', + 'ask_encryption' => 'Encryption method to use', + ], + 'database' => [ + 'host_warning' => 'It is highly recommended to not use "localhost" as your database host as we have seen frequent socket connection issues. If you want to use a local connection you should be using "127.0.0.1".', + 'host' => 'Database Host', + 'port' => 'Database Port', + 'database' => 'Database Name', + 'username_warning' => 'Using the "root" account for MySQL connections is not only highly frowned upon, it is also not allowed by this application. You\'ll need to have created a MySQL user for this software.', + 'username' => 'Database Username', + 'password_defined' => 'It appears you already have a MySQL connection password defined, would you like to change it?', + 'password' => 'Database Password', + 'connection_error' => 'Unable to connect to the MySQL server using the provided credentials. The error returned was ":error".', + 'creds_not_saved' => 'Your connection credentials have NOT been saved. You will need to provide valid connection information before proceeding.', + 'try_again' => 'Go back and try again?', + ], + 'app' => [ + 'settings' => 'Enable UI based settings editor?', + 'author' => 'Egg Author Email', + 'author_help' => 'Provide the email address that eggs exported by this Panel should be from. This should be a valid email address.', + 'app_url_help' => 'The application URL MUST begin with https:// or http:// depending on if you are using SSL or not. If you do not include the scheme your emails and other content will link to the wrong location.', + 'app_url' => 'Application URL', + 'timezone_help' => 'The timezone should match one of PHP\'s supported timezones. If you are unsure, please reference http://php.net/manual/en/timezones.php.', + 'timezone' => 'Application Timezone', + 'cache_driver' => 'Cache Driver', + 'session_driver' => 'Session Driver', + 'queue_driver' => 'Queue Driver', + 'using_redis' => 'You\'ve selected the Redis driver for one or more options, please provide valid connection information below. In most cases you can use the defaults provided unless you have modified your setup.', + 'redis_host' => 'Redis Host', + 'redis_password' => 'Redis Password', + 'redis_pass_help' => 'By default a Redis server instance has no password as it is running locally and inaccessible to the outside world. If this is the case, simply hit enter without entering a value.', + 'redis_port' => 'Redis Port', + 'redis_pass_defined' => 'It seems a password is already defined for Redis, would you like to change it?', + ], + ], +]; From 100d4ee726764e256f602370a6b0e53c9e3e9f21 Mon Sep 17 00:00:00 2001 From: DaneEveritt Date: Thu, 12 May 2022 17:53:29 -0400 Subject: [PATCH 021/458] Remove more unnecessary translations --- .../Environment/AppSettingsCommand.php | 101 +++++++----------- .../Environment/DatabaseSettingsCommand.php | 52 ++++----- resources/lang/en/command/messages.php | 31 ------ 3 files changed, 58 insertions(+), 126 deletions(-) diff --git a/app/Console/Commands/Environment/AppSettingsCommand.php b/app/Console/Commands/Environment/AppSettingsCommand.php index 59784e0f36..8d684bb2e9 100644 --- a/app/Console/Commands/Environment/AppSettingsCommand.php +++ b/app/Console/Commands/Environment/AppSettingsCommand.php @@ -1,32 +1,23 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Console\Commands\Environment; use DateTimeZone; use Illuminate\Console\Command; use Illuminate\Contracts\Console\Kernel; -use Illuminate\Validation\Factory as ValidatorFactory; use Pterodactyl\Traits\Commands\EnvironmentWriterTrait; -use Illuminate\Contracts\Config\Repository as ConfigRepository; class AppSettingsCommand extends Command { use EnvironmentWriterTrait; - public const ALLOWED_CACHE_DRIVERS = [ + public const CACHE_DRIVERS = [ 'redis' => 'Redis (recommended)', 'memcached' => 'Memcached', 'file' => 'Filesystem', ]; - public const ALLOWED_SESSION_DRIVERS = [ + public const SESSION_DRIVERS = [ 'redis' => 'Redis (recommended)', 'memcached' => 'Memcached', 'database' => 'MySQL Database', @@ -34,7 +25,7 @@ class AppSettingsCommand extends Command 'cookie' => 'Cookie', ]; - public const ALLOWED_QUEUE_DRIVERS = [ + public const QUEUE_DRIVERS = [ 'redis' => 'Redis (recommended)', 'database' => 'MySQL Database', 'sync' => 'Sync', @@ -45,11 +36,6 @@ class AppSettingsCommand extends Command */ protected $command; - /** - * @var \Illuminate\Contracts\Config\Repository - */ - protected $config; - /** * @var string */ @@ -79,13 +65,11 @@ class AppSettingsCommand extends Command /** * AppSettingsCommand constructor. */ - public function __construct(ConfigRepository $config, Kernel $command, ValidatorFactory $validator) + public function __construct(Kernel $command) { parent::__construct(); - $this->config = $config; $this->command = $command; - $this->validator = $validator; } /** @@ -95,67 +79,60 @@ public function __construct(ConfigRepository $config, Kernel $command, Validator */ public function handle() { - if (empty($this->config->get('hashids.salt')) || $this->option('new-salt')) { + if (empty(config('hashids.salt')) || $this->option('new-salt')) { $this->variables['HASHIDS_SALT'] = str_random(20); } - $this->output->comment(trans('command/messages.environment.app.author_help')); + $this->output->comment('Provide the email address that eggs exported by this Panel should be from. This should be a valid email address.'); $this->variables['APP_SERVICE_AUTHOR'] = $this->option('author') ?? $this->ask( - trans('command/messages.environment.app.author'), - $this->config->get('pterodactyl.service.author', 'unknown@unknown.com') - ); - - $validator = $this->validator->make( - ['email' => $this->variables['APP_SERVICE_AUTHOR']], - ['email' => 'email'] + 'Egg Author Email', + config('pterodactyl.service.author', 'unknown@unknown.com') ); - if ($validator->fails()) { - foreach ($validator->errors()->all() as $error) { - $this->output->error($error); - } + if (!filter_var($this->variables['APP_SERVICE_AUTHOR'], FILTER_VALIDATE_EMAIL)) { + $this->output->error('The service author email provided is invalid.'); return 1; } - $this->output->comment(trans('command/messages.environment.app.app_url_help')); + $this->output->comment('The application URL MUST begin with https:// or http:// depending on if you are using SSL or not. If you do not include the scheme your emails and other content will link to the wrong location.'); $this->variables['APP_URL'] = $this->option('url') ?? $this->ask( - trans('command/messages.environment.app.app_url'), - $this->config->get('app.url', 'http://example.org') + 'Application URL', + config('app.url', 'http://example.org') ); - $this->output->comment(trans('command/messages.environment.app.timezone_help')); + $this->output->comment('The timezone should match one of PHP\'s supported timezones. If you are unsure, please reference http://php.net/manual/en/timezones.php.'); $this->variables['APP_TIMEZONE'] = $this->option('timezone') ?? $this->anticipate( - trans('command/messages.environment.app.timezone'), + 'Application Timezone', DateTimeZone::listIdentifiers(DateTimeZone::ALL), - $this->config->get('app.timezone') + config('app.timezone') ); - $selected = $this->config->get('cache.default', 'redis'); + $selected = config('cache.default', 'redis'); $this->variables['CACHE_DRIVER'] = $this->option('cache') ?? $this->choice( - trans('command/messages.environment.app.cache_driver'), - self::ALLOWED_CACHE_DRIVERS, - array_key_exists($selected, self::ALLOWED_CACHE_DRIVERS) ? $selected : null + 'Cache Driver', + self::CACHE_DRIVERS, + array_key_exists($selected, self::CACHE_DRIVERS) ? $selected : null ); - $selected = $this->config->get('session.driver', 'redis'); + $selected = config('session.driver', 'redis'); $this->variables['SESSION_DRIVER'] = $this->option('session') ?? $this->choice( - trans('command/messages.environment.app.session_driver'), - self::ALLOWED_SESSION_DRIVERS, - array_key_exists($selected, self::ALLOWED_SESSION_DRIVERS) ? $selected : null + 'Session Driver', + self::SESSION_DRIVERS, + array_key_exists($selected, self::SESSION_DRIVERS) ? $selected : null ); - $selected = $this->config->get('queue.default', 'redis'); + $selected = config('queue.default', 'redis'); $this->variables['QUEUE_CONNECTION'] = $this->option('queue') ?? $this->choice( - trans('command/messages.environment.app.queue_driver'), - self::ALLOWED_QUEUE_DRIVERS, - array_key_exists($selected, self::ALLOWED_QUEUE_DRIVERS) ? $selected : null + 'Queue Driver', + self::QUEUE_DRIVERS, + array_key_exists($selected, self::QUEUE_DRIVERS) ? $selected : null ); if (!is_null($this->option('settings-ui'))) { $this->variables['APP_ENVIRONMENT_ONLY'] = $this->option('settings-ui') == 'true' ? 'false' : 'true'; } else { - $this->variables['APP_ENVIRONMENT_ONLY'] = $this->confirm(trans('command/messages.environment.app.settings'), true) ? 'false' : 'true'; + $this->variables['APP_ENVIRONMENT_ONLY'] = $this->confirm('Enable UI based settings editor?', true) ? 'false' : 'true'; } // Make sure session cookies are set as "secure" when using HTTPS @@ -183,22 +160,22 @@ private function checkForRedis() return; } - $this->output->note(trans('command/messages.environment.app.using_redis')); + $this->output->note('You\'ve selected the Redis driver for one or more options, please provide valid connection information below. In most cases you can use the defaults provided unless you have modified your setup.'); $this->variables['REDIS_HOST'] = $this->option('redis-host') ?? $this->ask( - trans('command/messages.environment.app.redis_host'), - $this->config->get('database.redis.default.host') + 'Redis Host', + config('database.redis.default.host') ); $askForRedisPassword = true; - if (!empty($this->config->get('database.redis.default.password'))) { - $this->variables['REDIS_PASSWORD'] = $this->config->get('database.redis.default.password'); - $askForRedisPassword = $this->confirm(trans('command/messages.environment.app.redis_pass_defined')); + if (!empty(config('database.redis.default.password'))) { + $this->variables['REDIS_PASSWORD'] = config('database.redis.default.password'); + $askForRedisPassword = $this->confirm('It seems a password is already defined for Redis, would you like to change it?'); } if ($askForRedisPassword) { - $this->output->comment(trans('command/messages.environment.app.redis_pass_help')); + $this->output->comment('By default a Redis server instance has no password as it is running locally and inaccessible to the outside world. If this is the case, simply hit enter without entering a value.'); $this->variables['REDIS_PASSWORD'] = $this->option('redis-pass') ?? $this->output->askHidden( - trans('command/messages.environment.app.redis_password') + 'Redis Password' ); } @@ -207,8 +184,8 @@ private function checkForRedis() } $this->variables['REDIS_PORT'] = $this->option('redis-port') ?? $this->ask( - trans('command/messages.environment.app.redis_port'), - $this->config->get('database.redis.default.port') + 'Redis Port', + config('database.redis.default.port') ); } } diff --git a/app/Console/Commands/Environment/DatabaseSettingsCommand.php b/app/Console/Commands/Environment/DatabaseSettingsCommand.php index b983fcc9f6..6254eaf20e 100644 --- a/app/Console/Commands/Environment/DatabaseSettingsCommand.php +++ b/app/Console/Commands/Environment/DatabaseSettingsCommand.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Console\Commands\Environment; @@ -14,17 +7,11 @@ use Illuminate\Contracts\Console\Kernel; use Illuminate\Database\DatabaseManager; use Pterodactyl\Traits\Commands\EnvironmentWriterTrait; -use Illuminate\Contracts\Config\Repository as ConfigRepository; class DatabaseSettingsCommand extends Command { use EnvironmentWriterTrait; - /** - * @var \Illuminate\Contracts\Config\Repository - */ - protected $config; - /** * @var \Illuminate\Contracts\Console\Kernel */ @@ -58,11 +45,10 @@ class DatabaseSettingsCommand extends Command /** * DatabaseSettingsCommand constructor. */ - public function __construct(ConfigRepository $config, DatabaseManager $database, Kernel $console) + public function __construct(DatabaseManager $database, Kernel $console) { parent::__construct(); - $this->config = $config; $this->console = $console; $this->database = $database; } @@ -76,45 +62,45 @@ public function __construct(ConfigRepository $config, DatabaseManager $database, */ public function handle() { - $this->output->note(trans('command/messages.environment.database.host_warning')); + $this->output->note('It is highly recommended to not use "localhost" as your database host as we have seen frequent socket connection issues. If you want to use a local connection you should be using "127.0.0.1".'); $this->variables['DB_HOST'] = $this->option('host') ?? $this->ask( - trans('command/messages.environment.database.host'), - $this->config->get('database.connections.mysql.host', '127.0.0.1') + 'Database Host', + config('database.connections.mysql.host', '127.0.0.1') ); $this->variables['DB_PORT'] = $this->option('port') ?? $this->ask( - trans('command/messages.environment.database.port'), - $this->config->get('database.connections.mysql.port', 3306) + 'Database Port', + config('database.connections.mysql.port', 3306) ); $this->variables['DB_DATABASE'] = $this->option('database') ?? $this->ask( - trans('command/messages.environment.database.database'), - $this->config->get('database.connections.mysql.database', 'panel') + 'Database Name', + config('database.connections.mysql.database', 'panel') ); - $this->output->note(trans('command/messages.environment.database.username_warning')); + $this->output->note('Using the "root" account for MySQL connections is not only highly frowned upon, it is also not allowed by this application. You\'ll need to have created a MySQL user for this software.'); $this->variables['DB_USERNAME'] = $this->option('username') ?? $this->ask( - trans('command/messages.environment.database.username'), - $this->config->get('database.connections.mysql.username', 'pterodactyl') + 'Database Username', + config('database.connections.mysql.username', 'pterodactyl') ); $askForMySQLPassword = true; - if (!empty($this->config->get('database.connections.mysql.password')) && $this->input->isInteractive()) { - $this->variables['DB_PASSWORD'] = $this->config->get('database.connections.mysql.password'); - $askForMySQLPassword = $this->confirm(trans('command/messages.environment.database.password_defined')); + if (!empty(config('database.connections.mysql.password')) && $this->input->isInteractive()) { + $this->variables['DB_PASSWORD'] = config('database.connections.mysql.password'); + $askForMySQLPassword = $this->confirm('It appears you already have a MySQL connection password defined, would you like to change it?'); } if ($askForMySQLPassword) { - $this->variables['DB_PASSWORD'] = $this->option('password') ?? $this->secret(trans('command/messages.environment.database.password')); + $this->variables['DB_PASSWORD'] = $this->option('password') ?? $this->secret('Database Password'); } try { $this->testMySQLConnection(); } catch (PDOException $exception) { - $this->output->error(trans('command/messages.environment.database.connection_error', ['error' => $exception->getMessage()])); - $this->output->error(trans('command/messages.environment.database.creds_not_saved')); + $this->output->error(sprintf('Unable to connect to the MySQL server using the provided credentials. The error returned was "%s".', $exception->getMessage())); + $this->output->error('Your connection credentials have NOT been saved. You will need to provide valid connection information before proceeding.'); - if ($this->confirm(trans('command/messages.environment.database.try_again'))) { + if ($this->confirm('Go back and try again?')) { $this->database->disconnect('_pterodactyl_command_test'); return $this->handle(); @@ -135,7 +121,7 @@ public function handle() */ private function testMySQLConnection() { - $this->config->set('database.connections._pterodactyl_command_test', [ + config()->set('database.connections._pterodactyl_command_test', [ 'driver' => 'mysql', 'host' => $this->variables['DB_HOST'], 'port' => $this->variables['DB_PORT'], diff --git a/resources/lang/en/command/messages.php b/resources/lang/en/command/messages.php index b02fa8c298..a4a3aafdf1 100644 --- a/resources/lang/en/command/messages.php +++ b/resources/lang/en/command/messages.php @@ -62,36 +62,5 @@ 'ask_mail_name' => 'Name that emails should appear from', 'ask_encryption' => 'Encryption method to use', ], - 'database' => [ - 'host_warning' => 'It is highly recommended to not use "localhost" as your database host as we have seen frequent socket connection issues. If you want to use a local connection you should be using "127.0.0.1".', - 'host' => 'Database Host', - 'port' => 'Database Port', - 'database' => 'Database Name', - 'username_warning' => 'Using the "root" account for MySQL connections is not only highly frowned upon, it is also not allowed by this application. You\'ll need to have created a MySQL user for this software.', - 'username' => 'Database Username', - 'password_defined' => 'It appears you already have a MySQL connection password defined, would you like to change it?', - 'password' => 'Database Password', - 'connection_error' => 'Unable to connect to the MySQL server using the provided credentials. The error returned was ":error".', - 'creds_not_saved' => 'Your connection credentials have NOT been saved. You will need to provide valid connection information before proceeding.', - 'try_again' => 'Go back and try again?', - ], - 'app' => [ - 'settings' => 'Enable UI based settings editor?', - 'author' => 'Egg Author Email', - 'author_help' => 'Provide the email address that eggs exported by this Panel should be from. This should be a valid email address.', - 'app_url_help' => 'The application URL MUST begin with https:// or http:// depending on if you are using SSL or not. If you do not include the scheme your emails and other content will link to the wrong location.', - 'app_url' => 'Application URL', - 'timezone_help' => 'The timezone should match one of PHP\'s supported timezones. If you are unsure, please reference http://php.net/manual/en/timezones.php.', - 'timezone' => 'Application Timezone', - 'cache_driver' => 'Cache Driver', - 'session_driver' => 'Session Driver', - 'queue_driver' => 'Queue Driver', - 'using_redis' => 'You\'ve selected the Redis driver for one or more options, please provide valid connection information below. In most cases you can use the defaults provided unless you have modified your setup.', - 'redis_host' => 'Redis Host', - 'redis_password' => 'Redis Password', - 'redis_pass_help' => 'By default a Redis server instance has no password as it is running locally and inaccessible to the outside world. If this is the case, simply hit enter without entering a value.', - 'redis_port' => 'Redis Port', - 'redis_pass_defined' => 'It seems a password is already defined for Redis, would you like to change it?', - ], ], ]; From 3f47d7a12c62849e3bf8e8b4edb6231a9068fafa Mon Sep 17 00:00:00 2001 From: DaneEveritt Date: Fri, 13 May 2022 21:30:16 -0400 Subject: [PATCH 022/458] Allow returning the node configuration from the CLI; closes pterodactyl/panel#4047 --- .../Node/NodeConfigurationCommand.php | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 app/Console/Commands/Node/NodeConfigurationCommand.php diff --git a/app/Console/Commands/Node/NodeConfigurationCommand.php b/app/Console/Commands/Node/NodeConfigurationCommand.php new file mode 100644 index 0000000000..7cb31ba9ab --- /dev/null +++ b/app/Console/Commands/Node/NodeConfigurationCommand.php @@ -0,0 +1,38 @@ +findOrFail($this->argument('node')); + + $format = $this->option('format'); + if (!in_array($format, ['yaml', 'yml', 'json'])) { + $this->error('Invalid format specified. Valid options are "yaml" and "json".'); + + return 1; + } + + if ($format === 'json') { + $this->output->write($node->getJsonConfiguration(true)); + } else { + $this->output->write($node->getYamlConfiguration()); + } + + $this->output->newLine(); + + return 0; + } +} From 97a79590962202be740eec2e90b2dbd1f26786b3 Mon Sep 17 00:00:00 2001 From: DaneEveritt Date: Fri, 13 May 2022 21:49:06 -0400 Subject: [PATCH 023/458] Support outputting all of the nodes on the instance --- .../Node/NodeConfigurationCommand.php | 10 ++++-- app/Console/Commands/Node/NodeListCommand.php | 34 +++++++++++++++++++ 2 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 app/Console/Commands/Node/NodeListCommand.php diff --git a/app/Console/Commands/Node/NodeConfigurationCommand.php b/app/Console/Commands/Node/NodeConfigurationCommand.php index 7cb31ba9ab..fdd06417b8 100644 --- a/app/Console/Commands/Node/NodeConfigurationCommand.php +++ b/app/Console/Commands/Node/NodeConfigurationCommand.php @@ -15,14 +15,20 @@ class NodeConfigurationCommand extends Command public function handle() { + $column = ctype_digit((string) $this->argument('node')) ? 'id' : 'uuid'; + /** @var \Pterodactyl\Models\Node $node */ - $node = Node::query()->findOrFail($this->argument('node')); + $node = Node::query()->where($column, $this->argument('node'))->firstOr(function () { + $this->error('The selected node does not exist.'); + + exit(1); + }); $format = $this->option('format'); if (!in_array($format, ['yaml', 'yml', 'json'])) { $this->error('Invalid format specified. Valid options are "yaml" and "json".'); - return 1; + exit(1); } if ($format === 'json') { diff --git a/app/Console/Commands/Node/NodeListCommand.php b/app/Console/Commands/Node/NodeListCommand.php new file mode 100644 index 0000000000..01ed4fde4b --- /dev/null +++ b/app/Console/Commands/Node/NodeListCommand.php @@ -0,0 +1,34 @@ +with('location')->get()->map(function (Node $node) { + return [ + 'id' => $node->id, + 'uuid' => $node->uuid, + 'name' => $node->name, + 'location' => $node->location->short, + 'host' => $node->getConnectionAddress(), + ]; + }); + + if ($this->option('format') === 'json') { + $this->output->write($nodes->toJson(JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); + } else { + $this->table(['ID', 'UUID', 'Name', 'Location', 'Host'], $nodes->toArray()); + } + + $this->output->newLine(); + + return 0; + } +} From 3e1f70570b955e2af3aef1a54fb49d0f7ef56823 Mon Sep 17 00:00:00 2001 From: DaneEveritt Date: Fri, 13 May 2022 21:54:24 -0400 Subject: [PATCH 024/458] Update CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 08ad949ab9..6eeff7c3ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,8 @@ using these eggs should be updated to account for the new format. * A new cron cheatsheet has been added which appears when creating a schedule. * Adds support for filtering the `/api/application/nodes/:id/allocations` endpoint using `?filter[server_id]=0` to only return allocations that are not currently assigned to a server on that node. * Adds support for naming docker image values in an Egg to improve front-end display capabilities. +* Adds command to return the configuration for a specific node in both YAML and JSON format (`php artisan p:node:configuration`). +* Adds command to return a list of all nodes available on the Panel in both table and JSON format (`php artisan p:node:list`). ### Removed * Removes Google Analytics from the front end code. From 8791d681bcf9e3d6ac82e36fcafef9904523f0ee Mon Sep 17 00:00:00 2001 From: DaneEveritt Date: Fri, 13 May 2022 22:09:20 -0400 Subject: [PATCH 025/458] Fix server image selection not... existing --- public/themes/pterodactyl/js/admin/new-server.js | 9 +++++---- resources/views/admin/servers/view/startup.blade.php | 10 +++++----- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/public/themes/pterodactyl/js/admin/new-server.js b/public/themes/pterodactyl/js/admin/new-server.js index 39818963d7..db138cbde5 100644 --- a/public/themes/pterodactyl/js/admin/new-server.js +++ b/public/themes/pterodactyl/js/admin/new-server.js @@ -82,12 +82,13 @@ $('#pEggId').on('change', function (event) { let parentChain = _.get(Pterodactyl.nests, $('#pNestId').val(), null); let objectChain = _.get(parentChain, 'eggs.' + $(this).val(), null); - const images = _.get(objectChain, 'docker_images', []) + const images = _.get(objectChain, 'docker_images', {}) $('#pDefaultContainer').html(''); - for (let i = 0; i < images.length; i++) { + const keys = Object.keys(images); + for (let i = 0; i < keys.length; i++) { let opt = document.createElement('option'); - opt.value = images[i]; - opt.innerHTML = images[i]; + opt.value = images[keys[i]]; + opt.innerHTML = keys[i] + " (" + images[keys[i]] + ")"; $('#pDefaultContainer').append(opt); } diff --git a/resources/views/admin/servers/view/startup.blade.php b/resources/views/admin/servers/view/startup.blade.php index 389f8fc06a..3dccdfda23 100644 --- a/resources/views/admin/servers/view/startup.blade.php +++ b/resources/views/admin/servers/view/startup.blade.php @@ -118,15 +118,15 @@ var parentChain = _.get(Pterodactyl.nests, $("#pNestId").val()); var objectChain = _.get(parentChain, 'eggs.' + selectedEgg); - $('#setDefaultImage').html(_.get(objectChain, 'docker_images.0', 'undefined')); const images = _.get(objectChain, 'docker_images', []) $('#pDockerImage').html(''); - for (let i = 0; i < images.length; i++) { + const keys = Object.keys(images); + for (let i = 0; i < keys.length; i++) { let opt = document.createElement('option'); - opt.value = images[i]; - opt.innerHTML = images[i]; + opt.value = images[keys[i]]; + opt.innerHTML = keys[i] + " (" + images[keys[i]] + ")"; if (objectChain.id === parseInt(Pterodactyl.server.egg_id) && Pterodactyl.server.image == opt.value) { - opt.checked = true + opt.selected = true } $('#pDockerImage').append(opt); } From 62b178ed0297549be9eb009c968d5d47d4fe8ffb Mon Sep 17 00:00:00 2001 From: DaneEveritt Date: Fri, 13 May 2022 23:00:59 -0400 Subject: [PATCH 026/458] Show network usage on the server console view --- .babel-plugin-macrosrc.js | 4 +- CHANGELOG.md | 1 + .../components/server/ServerDetailsBlock.tsx | 29 +++- .../scripts/components/server/StatGraphs.tsx | 155 +++++++++--------- resources/scripts/helpers.ts | 2 - 5 files changed, 104 insertions(+), 87 deletions(-) diff --git a/.babel-plugin-macrosrc.js b/.babel-plugin-macrosrc.js index 4bcdbb88b7..feea845258 100644 --- a/.babel-plugin-macrosrc.js +++ b/.babel-plugin-macrosrc.js @@ -5,7 +5,7 @@ module.exports = { }, styledComponents: { pure: true, - displayName: false, - fileName: false, + displayName: true, + fileName: true, }, }; diff --git a/CHANGELOG.md b/CHANGELOG.md index 6eeff7c3ee..78a426356c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ using these eggs should be updated to account for the new format. * Adds support for naming docker image values in an Egg to improve front-end display capabilities. * Adds command to return the configuration for a specific node in both YAML and JSON format (`php artisan p:node:configuration`). * Adds command to return a list of all nodes available on the Panel in both table and JSON format (`php artisan p:node:list`). +* Adds server network (inbound/outbound) usage graphs to the console screen. ### Removed * Removes Google Analytics from the front end code. diff --git a/resources/scripts/components/server/ServerDetailsBlock.tsx b/resources/scripts/components/server/ServerDetailsBlock.tsx index 34d975b815..3eb5f01e96 100644 --- a/resources/scripts/components/server/ServerDetailsBlock.tsx +++ b/resources/scripts/components/server/ServerDetailsBlock.tsx @@ -1,20 +1,24 @@ import React, { useEffect, useState } from 'react'; import tw, { TwStyle } from 'twin.macro'; -import { faCircle, faEthernet, faHdd, faMemory, faMicrochip, faServer } from '@fortawesome/free-solid-svg-icons'; +import { + faArrowCircleDown, + faArrowCircleUp, + faCircle, + faEthernet, + faHdd, + faMemory, + faMicrochip, + faServer, +} from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { bytesToHuman, megabytesToHuman, formatIp } from '@/helpers'; +import { bytesToHuman, formatIp, megabytesToHuman } from '@/helpers'; import TitledGreyBox from '@/components/elements/TitledGreyBox'; import { ServerContext } from '@/state/server'; import CopyOnClick from '@/components/elements/CopyOnClick'; import { SocketEvent, SocketRequest } from '@/components/server/events'; import UptimeDuration from '@/components/server/UptimeDuration'; -interface Stats { - memory: number; - cpu: number; - disk: number; - uptime: number; -} +type Stats = Record<'memory' | 'cpu' | 'disk' | 'uptime' | 'rx' | 'tx', number>; function statusToColor (status: string | null, installing: boolean): TwStyle { if (installing) { @@ -32,7 +36,7 @@ function statusToColor (status: string | null, installing: boolean): TwStyle { } const ServerDetailsBlock = () => { - const [ stats, setStats ] = useState({ memory: 0, cpu: 0, disk: 0, uptime: 0 }); + const [ stats, setStats ] = useState({ memory: 0, cpu: 0, disk: 0, uptime: 0, tx: 0, rx: 0 }); const status = ServerContext.useStoreState(state => state.status.value); const connected = ServerContext.useStoreState(state => state.socket.connected); @@ -50,6 +54,8 @@ const ServerDetailsBlock = () => { memory: stats.memory_bytes, cpu: stats.cpu_absolute, disk: stats.disk_bytes, + tx: stats.network.tx_bytes, + rx: stats.network.rx_bytes, uptime: stats.uptime || 0, }); }; @@ -115,6 +121,11 @@ const ServerDetailsBlock = () => {  {bytesToHuman(stats.disk)} / {diskLimit}

+

+ + {bytesToHuman(stats.tx)} + {bytesToHuman(stats.rx)} +

); }; diff --git a/resources/scripts/components/server/StatGraphs.tsx b/resources/scripts/components/server/StatGraphs.tsx index 8e66f393ab..00e1c511db 100644 --- a/resources/scripts/components/server/StatGraphs.tsx +++ b/resources/scripts/components/server/StatGraphs.tsx @@ -1,15 +1,14 @@ -import React, { useCallback, useState } from 'react'; +import React, { useCallback, useRef, useState } from 'react'; import Chart, { ChartConfiguration } from 'chart.js'; import { ServerContext } from '@/state/server'; -import { bytesToMegabytes } from '@/helpers'; import merge from 'deepmerge'; import TitledGreyBox from '@/components/elements/TitledGreyBox'; -import { faMemory, faMicrochip } from '@fortawesome/free-solid-svg-icons'; +import { faEthernet, faMemory, faMicrochip } from '@fortawesome/free-solid-svg-icons'; import tw from 'twin.macro'; import { SocketEvent } from '@/components/server/events'; import useWebsocketEvent from '@/plugins/useWebsocketEvent'; -const chartDefaults = (ticks?: Chart.TickOptions | undefined): ChartConfiguration => ({ +const chartDefaults = (ticks?: Chart.TickOptions): ChartConfiguration => ({ type: 'line', options: { legend: { @@ -69,38 +68,43 @@ const chartDefaults = (ticks?: Chart.TickOptions | undefined): ChartConfiguratio }, }); -export default () => { - const status = ServerContext.useStoreState(state => state.status.value); - const limits = ServerContext.useStoreState(state => state.server.data!.limits); +type ChartState = [ (node: HTMLCanvasElement | null) => void, Chart | undefined ]; - const [ memory, setMemory ] = useState(); - const [ cpu, setCpu ] = useState(); +/** + * Creates an element ref and a chart instance. + */ +const useChart = (options?: Chart.TickOptions): ChartState => { + const [ chart, setChart ] = useState(); - const memoryRef = useCallback<(node: HTMLCanvasElement | null) => void>(node => { - if (!node) { - return; - } + const ref = useCallback<(node: HTMLCanvasElement | null) => void>(node => { + if (!node) return; + + const chart = new Chart(node.getContext('2d')!, chartDefaults(options)); - setMemory( - new Chart(node.getContext('2d')!, chartDefaults({ - callback: (value) => `${value}Mb `, - suggestedMax: limits.memory, - })), - ); + setChart(chart); }, []); - const cpuRef = useCallback<(node: HTMLCanvasElement | null) => void>(node => { - if (!node) { - return; - } + return [ ref, chart ]; +}; - setCpu( - new Chart(node.getContext('2d')!, chartDefaults({ - callback: (value) => `${value}% `, - suggestedMax: limits.cpu, - })), - ); - }, []); +const updateChartDataset = (chart: Chart | null | undefined, value: Chart.ChartPoint & number): void => { + if (!chart || !chart.data?.datasets) return; + + const data = chart.data.datasets[0].data!; + data.push(value); + data.shift(); + chart.update({ lazy: true }); +}; + +export default () => { + const status = ServerContext.useStoreState(state => state.status.value); + const limits = ServerContext.useStoreState(state => state.server.data!.limits); + + const previous = useRef>({ tx: -1, rx: -1 }); + const [ cpuRef, cpu ] = useChart({ callback: (value) => `${value}% `, suggestedMax: limits.cpu }); + const [ memoryRef, memory ] = useChart({ callback: (value) => `${value}Mb `, suggestedMax: limits.memory }); + const [ txRef, tx ] = useChart({ callback: (value) => `${value}Kb/s ` }); + const [ rxRef, rx ] = useChart({ callback: (value) => `${value}Kb/s ` }); useWebsocketEvent(SocketEvent.STATS, (data: string) => { let stats: any = {}; @@ -110,54 +114,57 @@ export default () => { return; } - if (memory && memory.data.datasets) { - const data = memory.data.datasets[0].data!; - - data.push(bytesToMegabytes(stats.memory_bytes)); - data.shift(); + updateChartDataset(cpu, stats.cpu_absolute); + updateChartDataset(memory, Math.floor(stats.memory_bytes / 1024 / 1024)); + updateChartDataset(tx, previous.current.tx < 0 ? 0 : Math.max(0, stats.network.tx_bytes - previous.current.tx) / 1024); + updateChartDataset(rx, previous.current.rx < 0 ? 0 : Math.max(0, stats.network.rx_bytes - previous.current.rx) / 1024); - memory.update({ lazy: true }); - } - - if (cpu && cpu.data.datasets) { - const data = cpu.data.datasets[0].data!; - - data.push(stats.cpu_absolute); - data.shift(); - - cpu.update({ lazy: true }); - } + previous.current = { tx: stats.network.tx_bytes, rx: stats.network.rx_bytes }; }); return ( -
-
- - {status !== 'offline' ? - - : -

- Server is offline. -

- } -
-
-
- - {status !== 'offline' ? - - : -

- Server is offline. -

- } -
-
+
+ + {status !== 'offline' ? + + : +

+ Server is offline. +

+ } +
+ + {status !== 'offline' ? + + : +

+ Server is offline. +

+ } +
+ + {status !== 'offline' ? + + : +

+ Server is offline. +

+ } +
+ + {status !== 'offline' ? + + : +

+ Server is offline. +

+ } +
); }; diff --git a/resources/scripts/helpers.ts b/resources/scripts/helpers.ts index cd57c55b6c..67d0794273 100644 --- a/resources/scripts/helpers.ts +++ b/resources/scripts/helpers.ts @@ -1,5 +1,3 @@ -export const bytesToMegabytes = (bytes: number) => Math.floor(bytes / 1024 / 1024); - export const megabytesToBytes = (mb: number) => Math.floor(mb * 1024 * 1024); export function bytesToHuman (bytes: number): string { From 65f27d41a2fbf417c2ef3f3fafb7aa27d7aefd2b Mon Sep 17 00:00:00 2001 From: DaneEveritt Date: Sat, 14 May 2022 15:51:05 -0400 Subject: [PATCH 027/458] Switch to more recent Laravel route definition methods --- app/Http/Kernel.php | 11 +- app/Providers/RouteServiceProvider.php | 55 ++----- routes/admin.php | 218 +++++++++++++------------ routes/api-application.php | 85 +++++----- routes/api-client.php | 131 +++++++-------- routes/api-remote.php | 25 +-- routes/auth.php | 76 +++++---- routes/base.php | 11 +- routes/server.php | 10 -- 9 files changed, 296 insertions(+), 326 deletions(-) delete mode 100644 routes/server.php diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index cf7b4a8d35..ea4346aefd 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -2,9 +2,9 @@ namespace Pterodactyl\Http; -use Illuminate\Http\Middleware\TrustProxies; use Pterodactyl\Models\ApiKey; use Illuminate\Auth\Middleware\Authorize; +use Illuminate\Http\Middleware\TrustProxies; use Illuminate\Auth\Middleware\Authenticate; use Pterodactyl\Http\Middleware\TrimStrings; use Illuminate\Session\Middleware\StartSession; @@ -72,21 +72,18 @@ class Kernel extends HttpKernel IsValidJson::class, StartSession::class, AuthenticateSession::class, + VerifyCsrfToken::class, + ], + 'application-api' => [ ApiSubstituteBindings::class, 'api..key:' . ApiKey::TYPE_APPLICATION, AuthenticateApplicationUser::class, - VerifyCsrfToken::class, AuthenticateIPAccess::class, ], 'client-api' => [ - HandleStatelessRequest::class, - IsValidJson::class, - StartSession::class, - AuthenticateSession::class, SubstituteClientApiBindings::class, 'api..key:' . ApiKey::TYPE_ACCOUNT, AuthenticateIPAccess::class, - VerifyCsrfToken::class, // This is perhaps a little backwards with the Client API, but logically you'd be unable // to create/get an API key without first enabling 2FA on the account, so I suppose in the // end it makes sense. diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index 2dedacb4a1..0ccebcc64c 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -10,15 +10,6 @@ class RouteServiceProvider extends ServiceProvider { - /** - * This namespace is applied to the controller routes in your routes file. - * - * In addition, it is set as the URL generator's root namespace. - * - * @var string - */ - protected $namespace = 'Pterodactyl\Http\Controllers'; - /** * Define your route model bindings, pattern filters, etc. */ @@ -27,35 +18,23 @@ public function boot() $this->configureRateLimiting(); $this->routes(function () { - Route::middleware(['web', 'auth', 'csrf']) - ->namespace("$this->namespace\\Base") - ->group(base_path('routes/base.php')); - - Route::middleware(['web', 'auth', 'admin', 'csrf'])->prefix('/admin') - ->namespace("$this->namespace\\Admin") - ->group(base_path('routes/admin.php')); - - Route::middleware(['web', 'csrf'])->prefix('/auth') - ->namespace("$this->namespace\\Auth") - ->group(base_path('routes/auth.php')); - - Route::middleware(['web', 'csrf', 'auth', 'server', 'node.maintenance']) - ->prefix('/api/server/{server}') - ->namespace("$this->namespace\\Server") - ->group(base_path('routes/server.php')); - - Route::middleware(['api', 'throttle:api.application']) - ->prefix('/api/application') - ->namespace("$this->namespace\\Api\\Application") - ->group(base_path('routes/api-application.php')); - - Route::middleware(['client-api', 'throttle:api.client']) - ->prefix('/api/client') - ->namespace("$this->namespace\\Api\\Client") - ->group(base_path('routes/api-client.php')); - - Route::middleware(['daemon'])->prefix('/api/remote') - ->namespace("$this->namespace\\Api\\Remote") + Route::middleware(['web', 'csrf'])->group(function () { + Route::middleware('auth')->group(base_path('routes/base.php')); + Route::middleware('guest')->prefix('/auth')->group(base_path('routes/auth.php')); + Route::middleware(['auth', 'admin'])->prefix('/admin')->group(base_path('routes/admin.php')); + }); + + Route::middleware('api')->group(function () { + Route::middleware(['application-api', 'throttle:api.application']) + ->prefix('/api/application') + ->group(base_path('routes/api-application.php')); + + Route::middleware(['client-api', 'throttle:api.client']) + ->prefix('/api/client') + ->group(base_path('routes/api-client.php')); + }); + + Route::middleware('daemon')->prefix('/api/remote') ->group(base_path('routes/api-remote.php')); }); } diff --git a/routes/admin.php b/routes/admin.php index fb45e62945..d89ae51db0 100644 --- a/routes/admin.php +++ b/routes/admin.php @@ -1,9 +1,10 @@ name('admin.index'); +Route::get('/', [Admin\BaseController::class, 'index'])->name('admin.index'); /* |-------------------------------------------------------------------------- @@ -14,12 +15,12 @@ | */ Route::group(['prefix' => 'api'], function () { - Route::get('/', 'ApiController@index')->name('admin.api.index'); - Route::get('/new', 'ApiController@create')->name('admin.api.new'); + Route::get('/', [Admin\ApiController::class, 'index'])->name('admin.api.index'); + Route::get('/new', [Admin\ApiController::class, 'create'])->name('admin.api.new'); - Route::post('/new', 'ApiController@store'); + Route::post('/new', [Admin\ApiController::class, 'store']); - Route::delete('/revoke/{identifier}', 'ApiController@delete')->name('admin.api.delete'); + Route::delete('/revoke/{identifier}', [Admin\ApiController::class, 'delete'])->name('admin.api.delete'); }); /* @@ -31,11 +32,11 @@ | */ Route::group(['prefix' => 'locations'], function () { - Route::get('/', 'LocationController@index')->name('admin.locations'); - Route::get('/view/{location}', 'LocationController@view')->name('admin.locations.view'); + Route::get('/', [Admin\LocationController::class, 'index'])->name('admin.locations'); + Route::get('/view/{location}', [Admin\LocationController::class, 'view'])->name('admin.locations.view'); - Route::post('/', 'LocationController@create'); - Route::patch('/view/{location}', 'LocationController@update'); + Route::post('/', [Admin\LocationController::class, 'create']); + Route::patch('/view/{location}', [Admin\LocationController::class, 'update']); }); /* @@ -47,12 +48,12 @@ | */ Route::group(['prefix' => 'databases'], function () { - Route::get('/', 'DatabaseController@index')->name('admin.databases'); - Route::get('/view/{host}', 'DatabaseController@view')->name('admin.databases.view'); + Route::get('/', [Admin\DatabaseController::class, 'index'])->name('admin.databases'); + Route::get('/view/{host}', [Admin\DatabaseController::class, 'view'])->name('admin.databases.view'); - Route::post('/', 'DatabaseController@create'); - Route::patch('/view/{host}', 'DatabaseController@update'); - Route::delete('/view/{host}', 'DatabaseController@delete'); + Route::post('/', [Admin\DatabaseController::class, 'create']); + Route::patch('/view/{host}', [Admin\DatabaseController::class, 'update']); + Route::delete('/view/{host}', [Admin\DatabaseController::class, 'delete']); }); /* @@ -64,14 +65,15 @@ | */ Route::group(['prefix' => 'settings'], function () { - Route::get('/', 'Settings\IndexController@index')->name('admin.settings'); - Route::get('/mail', 'Settings\MailController@index')->name('admin.settings.mail'); - Route::get('/advanced', 'Settings\AdvancedController@index')->name('admin.settings.advanced'); - Route::post('/mail/test', 'Settings\MailController@test')->name('admin.settings.mail.test'); - - Route::patch('/', 'Settings\IndexController@update'); - Route::patch('/mail', 'Settings\MailController@update'); - Route::patch('/advanced', 'Settings\AdvancedController@update'); + Route::get('/', [Admin\Settings\IndexController::class, 'index'])->name('admin.settings'); + Route::get('/mail', [Admin\Settings\MailController::class, 'index'])->name('admin.settings.mail'); + Route::get('/advanced', [Admin\Settings\AdvancedController::class, 'index'])->name('admin.settings.advanced'); + + Route::post('/mail/test', [Admin\Settings\MailController::class, 'test'])->name('admin.settings.mail.test'); + + Route::patch('/', [Admin\Settings\IndexController::class, 'update']); + Route::patch('/mail', [Admin\Settings\MailController::class, 'update']); + Route::patch('/advanced', [Admin\Settings\AdvancedController::class, 'update']); }); /* @@ -83,15 +85,15 @@ | */ Route::group(['prefix' => 'users'], function () { - Route::get('/', 'UserController@index')->name('admin.users'); - Route::get('/accounts.json', 'UserController@json')->name('admin.users.json'); - Route::get('/new', 'UserController@create')->name('admin.users.new'); - Route::get('/view/{user}', 'UserController@view')->name('admin.users.view'); + Route::get('/', [Admin\UserController::class, 'index'])->name('admin.users'); + Route::get('/accounts.json', [Admin\UserController::class, 'json'])->name('admin.users.json'); + Route::get('/new', [Admin\UserController::class, 'create'])->name('admin.users.new'); + Route::get('/view/{user}', [Admin\UserController::class, 'view'])->name('admin.users.view'); - Route::post('/new', 'UserController@store'); - Route::patch('/view/{user}', 'UserController@update'); + Route::post('/new', [Admin\UserController::class, 'store']); - Route::delete('/view/{user}', 'UserController@delete'); + Route::patch('/view/{user}', [Admin\UserController::class, 'update']); + Route::delete('/view/{user}', [Admin\UserController::class, 'delete']); }); /* @@ -103,37 +105,37 @@ | */ Route::group(['prefix' => 'servers'], function () { - Route::get('/', 'Servers\ServerController@index')->name('admin.servers'); - Route::get('/new', 'Servers\CreateServerController@index')->name('admin.servers.new'); - Route::get('/view/{server}', 'Servers\ServerViewController@index')->name('admin.servers.view'); + Route::get('/', [Admin\Servers\ServerController::class, 'index'])->name('admin.servers'); + Route::get('/new', [Admin\Servers\CreateServerController::class, 'index'])->name('admin.servers.new'); + Route::get('/view/{server}', [Admin\Servers\ServerViewController::class, 'index'])->name('admin.servers.view'); Route::group(['middleware' => [ServerInstalled::class]], function () { - Route::get('/view/{server}/details', 'Servers\ServerViewController@details')->name('admin.servers.view.details'); - Route::get('/view/{server}/build', 'Servers\ServerViewController@build')->name('admin.servers.view.build'); - Route::get('/view/{server}/startup', 'Servers\ServerViewController@startup')->name('admin.servers.view.startup'); - Route::get('/view/{server}/database', 'Servers\ServerViewController@database')->name('admin.servers.view.database'); - Route::get('/view/{server}/mounts', 'Servers\ServerViewController@mounts')->name('admin.servers.view.mounts'); + Route::get('/view/{server}/details', [Admin\Servers\ServerViewController::class, 'details'])->name('admin.servers.view.details'); + Route::get('/view/{server}/build', [Admin\Servers\ServerViewController::class, 'build'])->name('admin.servers.view.build'); + Route::get('/view/{server}/startup', [Admin\Servers\ServerViewController::class, 'startup'])->name('admin.servers.view.startup'); + Route::get('/view/{server}/database', [Admin\Servers\ServerViewController::class, 'database'])->name('admin.servers.view.database'); + Route::get('/view/{server}/mounts', [Admin\Servers\ServerViewController::class, 'mounts'])->name('admin.servers.view.mounts'); }); - Route::get('/view/{server}/manage', 'Servers\ServerViewController@manage')->name('admin.servers.view.manage'); - Route::get('/view/{server}/delete', 'Servers\ServerViewController@delete')->name('admin.servers.view.delete'); - - Route::post('/new', 'Servers\CreateServerController@store'); - Route::post('/view/{server}/build', 'ServersController@updateBuild'); - Route::post('/view/{server}/startup', 'ServersController@saveStartup'); - Route::post('/view/{server}/database', 'ServersController@newDatabase'); - Route::post('/view/{server}/mounts/{mount}', 'ServersController@addMount')->name('admin.servers.view.mounts.toggle'); - Route::post('/view/{server}/manage/toggle', 'ServersController@toggleInstall')->name('admin.servers.view.manage.toggle'); - Route::post('/view/{server}/manage/suspension', 'ServersController@manageSuspension')->name('admin.servers.view.manage.suspension'); - Route::post('/view/{server}/manage/reinstall', 'ServersController@reinstallServer')->name('admin.servers.view.manage.reinstall'); - Route::post('/view/{server}/manage/transfer', 'Servers\ServerTransferController@transfer')->name('admin.servers.view.manage.transfer'); - Route::post('/view/{server}/delete', 'ServersController@delete'); - - Route::patch('/view/{server}/details', 'ServersController@setDetails'); - Route::patch('/view/{server}/database', 'ServersController@resetDatabasePassword'); - - Route::delete('/view/{server}/database/{database}/delete', 'ServersController@deleteDatabase')->name('admin.servers.view.database.delete'); - Route::delete('/view/{server}/mounts/{mount}', 'ServersController@deleteMount'); + Route::get('/view/{server}/manage', [Admin\Servers\ServerViewController::class, 'manage'])->name('admin.servers.view.manage'); + Route::get('/view/{server}/delete', [Admin\Servers\ServerViewController::class, 'delete'])->name('admin.servers.view.delete'); + + Route::post('/new', [Admin\Servers\CreateServerController::class, 'store']); + Route::post('/view/{server}/build', [Admin\ServersController::class, 'updateBuild']); + Route::post('/view/{server}/startup', [Admin\ServersController::class, 'saveStartup']); + Route::post('/view/{server}/database', [Admin\ServersController::class, 'newDatabase']); + Route::post('/view/{server}/mounts/{mount}', [Admin\ServersController::class, 'addMount'])->name('admin.servers.view.mounts.toggle'); + Route::post('/view/{server}/manage/toggle', [Admin\ServersController::class, 'toggleInstall'])->name('admin.servers.view.manage.toggle'); + Route::post('/view/{server}/manage/suspension', [Admin\ServersController::class, 'manageSuspension'])->name('admin.servers.view.manage.suspension'); + Route::post('/view/{server}/manage/reinstall', [Admin\ServersController::class, 'reinstallServer'])->name('admin.servers.view.manage.reinstall'); + Route::post('/view/{server}/manage/transfer', [Admin\Servers\ServerTransferController::class, 'transfer'])->name('admin.servers.view.manage.transfer'); + Route::post('/view/{server}/delete', [Admin\ServersController::class, 'delete']); + + Route::patch('/view/{server}/details', [Admin\ServersController::class, 'setDetails']); + Route::patch('/view/{server}/database', [Admin\ServersController::class, 'resetDatabasePassword']); + + Route::delete('/view/{server}/database/{database}/delete', [Admin\ServersController::class, 'deleteDatabase'])->name('admin.servers.view.database.delete'); + Route::delete('/view/{server}/mounts/{mount}', [Admin\ServersController::class, 'deleteMount']); }); /* @@ -145,26 +147,26 @@ | */ Route::group(['prefix' => 'nodes'], function () { - Route::get('/', 'Nodes\NodeController@index')->name('admin.nodes'); - Route::get('/new', 'NodesController@create')->name('admin.nodes.new'); - Route::get('/view/{node}', 'Nodes\NodeViewController@index')->name('admin.nodes.view'); - Route::get('/view/{node}/settings', 'Nodes\NodeViewController@settings')->name('admin.nodes.view.settings'); - Route::get('/view/{node}/configuration', 'Nodes\NodeViewController@configuration')->name('admin.nodes.view.configuration'); - Route::get('/view/{node}/allocation', 'Nodes\NodeViewController@allocations')->name('admin.nodes.view.allocation'); - Route::get('/view/{node}/servers', 'Nodes\NodeViewController@servers')->name('admin.nodes.view.servers'); - Route::get('/view/{node}/system-information', 'Nodes\SystemInformationController'); - - Route::post('/new', 'NodesController@store'); - Route::post('/view/{node}/allocation', 'NodesController@createAllocation'); - Route::post('/view/{node}/allocation/remove', 'NodesController@allocationRemoveBlock')->name('admin.nodes.view.allocation.removeBlock'); - Route::post('/view/{node}/allocation/alias', 'NodesController@allocationSetAlias')->name('admin.nodes.view.allocation.setAlias'); - Route::post('/view/{node}/settings/token', 'NodeAutoDeployController')->name('admin.nodes.view.configuration.token'); - - Route::patch('/view/{node}/settings', 'NodesController@updateSettings'); - - Route::delete('/view/{node}/delete', 'NodesController@delete')->name('admin.nodes.view.delete'); - Route::delete('/view/{node}/allocation/remove/{allocation}', 'NodesController@allocationRemoveSingle')->name('admin.nodes.view.allocation.removeSingle'); - Route::delete('/view/{node}/allocations', 'NodesController@allocationRemoveMultiple')->name('admin.nodes.view.allocation.removeMultiple'); + Route::get('/', [Admin\Nodes\NodeController::class, 'index'])->name('admin.nodes'); + Route::get('/new', [Admin\NodesController::class, 'create'])->name('admin.nodes.new'); + Route::get('/view/{node}', [Admin\Nodes\NodeViewController::class, 'index'])->name('admin.nodes.view'); + Route::get('/view/{node}/settings', [Admin\Nodes\NodeViewController::class, 'settings'])->name('admin.nodes.view.settings'); + Route::get('/view/{node}/configuration', [Admin\Nodes\NodeViewController::class, 'configuration'])->name('admin.nodes.view.configuration'); + Route::get('/view/{node}/allocation', [Admin\Nodes\NodeViewController::class, 'allocations'])->name('admin.nodes.view.allocation'); + Route::get('/view/{node}/servers', [Admin\Nodes\NodeViewController::class, 'servers'])->name('admin.nodes.view.servers'); + Route::get('/view/{node}/system-information', Admin\Nodes\SystemInformationController::class); + + Route::post('/new', [Admin\NodesController::class, 'store']); + Route::post('/view/{node}/allocation', [Admin\NodesController::class, 'createAllocation']); + Route::post('/view/{node}/allocation/remove', [Admin\NodesController::class, 'allocationRemoveBlock'])->name('admin.nodes.view.allocation.removeBlock'); + Route::post('/view/{node}/allocation/alias', [Admin\NodesController::class, 'allocationSetAlias'])->name('admin.nodes.view.allocation.setAlias'); + Route::post('/view/{node}/settings/token', Admin\NodeAutoDeployController::class)->name('admin.nodes.view.configuration.token'); + + Route::patch('/view/{node}/settings', [Admin\NodesController::class, 'updateSettings']); + + Route::delete('/view/{node}/delete', [Admin\NodesController::class, 'delete'])->name('admin.nodes.view.delete'); + Route::delete('/view/{node}/allocation/remove/{allocation}', [Admin\NodesController::class, 'allocationRemoveSingle'])->name('admin.nodes.view.allocation.removeSingle'); + Route::delete('/view/{node}/allocations', [Admin\NodesController::class, 'allocationRemoveMultiple'])->name('admin.nodes.view.allocation.removeMultiple'); }); /* @@ -176,17 +178,17 @@ | */ Route::group(['prefix' => 'mounts'], function () { - Route::get('/', 'MountController@index')->name('admin.mounts'); - Route::get('/view/{mount}', 'MountController@view')->name('admin.mounts.view'); + Route::get('/', [Admin\MountController::class, 'index'])->name('admin.mounts'); + Route::get('/view/{mount}', [Admin\MountController::class, 'view'])->name('admin.mounts.view'); - Route::post('/', 'MountController@create'); - Route::post('/{mount}/eggs', 'MountController@addEggs')->name('admin.mounts.eggs'); - Route::post('/{mount}/nodes', 'MountController@addNodes')->name('admin.mounts.nodes'); + Route::post('/', [Admin\MountController::class, 'create']); + Route::post('/{mount}/eggs', [Admin\MountController::class, 'addEggs'])->name('admin.mounts.eggs'); + Route::post('/{mount}/nodes', [Admin\MountController::class, 'addNodes'])->name('admin.mounts.nodes'); - Route::patch('/view/{mount}', 'MountController@update'); + Route::patch('/view/{mount}', [Admin\MountController::class, 'update']); - Route::delete('/{mount}/eggs/{egg_id}', 'MountController@deleteEgg'); - Route::delete('/{mount}/nodes/{node_id}', 'MountController@deleteNode'); + Route::delete('/{mount}/eggs/{egg_id}', [Admin\MountController::class, 'deleteEgg']); + Route::delete('/{mount}/nodes/{node_id}', [Admin\MountController::class, 'deleteNode']); }); /* @@ -198,28 +200,28 @@ | */ Route::group(['prefix' => 'nests'], function () { - Route::get('/', 'Nests\NestController@index')->name('admin.nests'); - Route::get('/new', 'Nests\NestController@create')->name('admin.nests.new'); - Route::get('/view/{nest}', 'Nests\NestController@view')->name('admin.nests.view'); - Route::get('/egg/new', 'Nests\EggController@create')->name('admin.nests.egg.new'); - Route::get('/egg/{egg}', 'Nests\EggController@view')->name('admin.nests.egg.view'); - Route::get('/egg/{egg}/export', 'Nests\EggShareController@export')->name('admin.nests.egg.export'); - Route::get('/egg/{egg}/variables', 'Nests\EggVariableController@view')->name('admin.nests.egg.variables'); - Route::get('/egg/{egg}/scripts', 'Nests\EggScriptController@index')->name('admin.nests.egg.scripts'); - - Route::post('/new', 'Nests\NestController@store'); - Route::post('/import', 'Nests\EggShareController@import')->name('admin.nests.egg.import'); - Route::post('/egg/new', 'Nests\EggController@store'); - Route::post('/egg/{egg}/variables', 'Nests\EggVariableController@store'); - - Route::put('/egg/{egg}', 'Nests\EggShareController@update'); - - Route::patch('/view/{nest}', 'Nests\NestController@update'); - Route::patch('/egg/{egg}', 'Nests\EggController@update'); - Route::patch('/egg/{egg}/scripts', 'Nests\EggScriptController@update'); - Route::patch('/egg/{egg}/variables/{variable}', 'Nests\EggVariableController@update')->name('admin.nests.egg.variables.edit'); - - Route::delete('/view/{nest}', 'Nests\NestController@destroy'); - Route::delete('/egg/{egg}', 'Nests\EggController@destroy'); - Route::delete('/egg/{egg}/variables/{variable}', 'Nests\EggVariableController@destroy'); + Route::get('/', [Admin\Nests\NestController::class, 'index'])->name('admin.nests'); + Route::get('/new', [Admin\Nests\NestController::class, 'create'])->name('admin.nests.new'); + Route::get('/view/{nest}', [Admin\Nests\NestController::class, 'view'])->name('admin.nests.view'); + Route::get('/egg/new', [Admin\Nests\EggController::class, 'create'])->name('admin.nests.egg.new'); + Route::get('/egg/{egg}', [Admin\Nests\EggController::class, 'view'])->name('admin.nests.egg.view'); + Route::get('/egg/{egg}/export', [Admin\Nests\EggShareController::class, 'export'])->name('admin.nests.egg.export'); + Route::get('/egg/{egg}/variables', [Admin\Nests\EggVariableController::class, 'view'])->name('admin.nests.egg.variables'); + Route::get('/egg/{egg}/scripts', [Admin\Nests\EggScriptController::class, 'index'])->name('admin.nests.egg.scripts'); + + Route::post('/new', [Admin\Nests\NestController::class, 'store']); + Route::post('/import', [Admin\Nests\EggShareController::class, 'import'])->name('admin.nests.egg.import'); + Route::post('/egg/new', [Admin\Nests\EggController::class, 'store']); + Route::post('/egg/{egg}/variables', [Admin\Nests\EggVariableController::class, 'store']); + + Route::put('/egg/{egg}', [Admin\Nests\EggShareController::class, 'update']); + + Route::patch('/view/{nest}', [Admin\Nests\NestController::class, 'update']); + Route::patch('/egg/{egg}', [Admin\Nests\EggController::class, 'update']); + Route::patch('/egg/{egg}/scripts', [Admin\Nests\EggScriptController::class, 'update']); + Route::patch('/egg/{egg}/variables/{variable}', [Admin\Nests\EggVariableController::class, 'update'])->name('admin.nests.egg.variables.edit'); + + Route::delete('/view/{nest}', [Admin\Nests\NestController::class, 'destroy']); + Route::delete('/egg/{egg}', [Admin\Nests\EggController::class, 'destroy']); + Route::delete('/egg/{egg}/variables/{variable}', [Admin\Nests\EggVariableController::class, 'destroy']); }); diff --git a/routes/api-application.php b/routes/api-application.php index 160f5f8b03..29d3d8aeb0 100644 --- a/routes/api-application.php +++ b/routes/api-application.php @@ -1,6 +1,7 @@ '/users'], function () { - Route::get('/', 'Users\UserController@index')->name('api.application.users'); - Route::get('/{user}', 'Users\UserController@view')->name('api.application.users.view'); - Route::get('/external/{external_id}', 'Users\ExternalUserController@index')->name('api.application.users.external'); + Route::get('/', [Application\Users\UserController::class, 'index'])->name('api.application.users'); + Route::get('/{user}', [Application\Users\UserController::class, 'view'])->name('api.application.users.view'); + Route::get('/external/{external_id}', [Application\Users\ExternalUserController::class, 'index'])->name('api.application.users.external'); - Route::post('/', 'Users\UserController@store'); - Route::patch('/{user}', 'Users\UserController@update'); + Route::post('/', [Application\Users\UserController::class, 'store']); + Route::patch('/{user}', [Application\Users\UserController::class, 'update']); - Route::delete('/{user}', 'Users\UserController@delete'); + Route::delete('/{user}', [Application\Users\UserController::class, 'delete']); }); /* @@ -31,20 +32,20 @@ | */ Route::group(['prefix' => '/nodes'], function () { - Route::get('/', 'Nodes\NodeController@index')->name('api.application.nodes'); - Route::get('/deployable', 'Nodes\NodeDeploymentController'); - Route::get('/{node}', 'Nodes\NodeController@view')->name('api.application.nodes.view'); - Route::get('/{node}/configuration', 'Nodes\NodeConfigurationController'); + Route::get('/', [Application\Nodes\NodeController::class, 'index'])->name('api.application.nodes'); + Route::get('/deployable', Application\Nodes\NodeDeploymentController::class); + Route::get('/{node}', [Application\Nodes\NodeController::class, 'view'])->name('api.application.nodes.view'); + Route::get('/{node}/configuration', Application\Nodes\NodeConfigurationController::class); - Route::post('/', 'Nodes\NodeController@store'); - Route::patch('/{node}', 'Nodes\NodeController@update'); + Route::post('/', [Application\Nodes\NodeController::class, 'store']); + Route::patch('/{node}', [Application\Nodes\NodeController::class, 'update']); - Route::delete('/{node}', 'Nodes\NodeController@delete'); + Route::delete('/{node}', [Application\Nodes\NodeController::class, 'delete']); Route::group(['prefix' => '/{node}/allocations'], function () { - Route::get('/', 'Nodes\AllocationController@index')->name('api.application.allocations'); - Route::post('/', 'Nodes\AllocationController@store'); - Route::delete('/{allocation}', 'Nodes\AllocationController@delete')->name('api.application.allocations.view'); + Route::get('/', [Application\Nodes\AllocationController::class, 'index'])->name('api.application.allocations'); + Route::post('/', [Application\Nodes\AllocationController::class, 'store']); + Route::delete('/{allocation}', [Application\Nodes\AllocationController::class, 'delete'])->name('api.application.allocations.view'); }); }); @@ -57,13 +58,13 @@ | */ Route::group(['prefix' => '/locations'], function () { - Route::get('/', 'Locations\LocationController@index')->name('api.applications.locations'); - Route::get('/{location}', 'Locations\LocationController@view')->name('api.application.locations.view'); + Route::get('/', [Application\Locations\LocationController::class, 'index'])->name('api.applications.locations'); + Route::get('/{location}', [Application\Locations\LocationController::class, 'view'])->name('api.application.locations.view'); - Route::post('/', 'Locations\LocationController@store'); - Route::patch('/{location}', 'Locations\LocationController@update'); + Route::post('/', [Application\Locations\LocationController::class, 'store']); + Route::patch('/{location}', [Application\Locations\LocationController::class, 'update']); - Route::delete('/{location}', 'Locations\LocationController@delete'); + Route::delete('/{location}', [Application\Locations\LocationController::class, 'delete']); }); /* @@ -75,31 +76,31 @@ | */ Route::group(['prefix' => '/servers'], function () { - Route::get('/', 'Servers\ServerController@index')->name('api.application.servers'); - Route::get('/{server}', 'Servers\ServerController@view')->name('api.application.servers.view'); - Route::get('/external/{external_id}', 'Servers\ExternalServerController@index')->name('api.application.servers.external'); + Route::get('/', [Application\Servers\ServerController::class, 'index'])->name('api.application.servers'); + Route::get('/{server}', [Application\Servers\ServerController::class, 'view'])->name('api.application.servers.view'); + Route::get('/external/{external_id}', [Application\Servers\ExternalServerController::class, 'index'])->name('api.application.servers.external'); - Route::patch('/{server}/details', 'Servers\ServerDetailsController@details')->name('api.application.servers.details'); - Route::patch('/{server}/build', 'Servers\ServerDetailsController@build')->name('api.application.servers.build'); - Route::patch('/{server}/startup', 'Servers\StartupController@index')->name('api.application.servers.startup'); + Route::patch('/{server}/details', [Application\Servers\ServerDetailsController::class, 'details'])->name('api.application.servers.details'); + Route::patch('/{server}/build', [Application\Servers\ServerDetailsController::class, 'build'])->name('api.application.servers.build'); + Route::patch('/{server}/startup', [Application\Servers\StartupController::class, 'index'])->name('api.application.servers.startup'); - Route::post('/', 'Servers\ServerController@store'); - Route::post('/{server}/suspend', 'Servers\ServerManagementController@suspend')->name('api.application.servers.suspend'); - Route::post('/{server}/unsuspend', 'Servers\ServerManagementController@unsuspend')->name('api.application.servers.unsuspend'); - Route::post('/{server}/reinstall', 'Servers\ServerManagementController@reinstall')->name('api.application.servers.reinstall'); + Route::post('/', [Application\Servers\ServerController::class, 'store']); + Route::post('/{server}/suspend', [Application\Servers\ServerManagementController::class, 'suspend'])->name('api.application.servers.suspend'); + Route::post('/{server}/unsuspend', [Application\Servers\ServerManagementController::class, 'unsuspend'])->name('api.application.servers.unsuspend'); + Route::post('/{server}/reinstall', [Application\Servers\ServerManagementController::class, 'reinstall'])->name('api.application.servers.reinstall'); - Route::delete('/{server}', 'Servers\ServerController@delete'); - Route::delete('/{server}/{force?}', 'Servers\ServerController@delete'); + Route::delete('/{server}', [Application\Servers\ServerController::class, 'delete']); + Route::delete('/{server}/{force?}', [Application\Servers\ServerController::class, 'delete']); // Database Management Endpoint Route::group(['prefix' => '/{server}/databases'], function () { - Route::get('/', 'Servers\DatabaseController@index')->name('api.application.servers.databases'); - Route::get('/{database}', 'Servers\DatabaseController@view')->name('api.application.servers.databases.view'); + Route::get('/', [Application\Servers\DatabaseController::class, 'index'])->name('api.application.servers.databases'); + Route::get('/{database}', [Application\Servers\DatabaseController::class, 'view'])->name('api.application.servers.databases.view'); - Route::post('/', 'Servers\DatabaseController@store'); - Route::post('/{database}/reset-password', 'Servers\DatabaseController@resetPassword'); + Route::post('/', [Application\Servers\DatabaseController::class, 'store']); + Route::post('/{database}/reset-password', [Application\Servers\DatabaseController::class, 'resetPassword']); - Route::delete('/{database}', 'Servers\DatabaseController@delete'); + Route::delete('/{database}', [Application\Servers\DatabaseController::class, 'delete']); }); }); @@ -112,12 +113,12 @@ | */ Route::group(['prefix' => '/nests'], function () { - Route::get('/', 'Nests\NestController@index')->name('api.application.nests'); - Route::get('/{nest}', 'Nests\NestController@view')->name('api.application.nests.view'); + Route::get('/', [Application\Nests\NestController::class, 'index'])->name('api.application.nests'); + Route::get('/{nest}', [Application\Nests\NestController::class, 'view'])->name('api.application.nests.view'); // Egg Management Endpoint Route::group(['prefix' => '/{nest}/eggs'], function () { - Route::get('/', 'Nests\EggController@index')->name('api.application.nests.eggs'); - Route::get('/{egg}', 'Nests\EggController@view')->name('api.application.nests.eggs.view'); + Route::get('/', [Application\Nests\EggController::class, 'index'])->name('api.application.nests.eggs'); + Route::get('/{egg}', [Application\Nests\EggController::class, 'view'])->name('api.application.nests.eggs.view'); }); }); diff --git a/routes/api-client.php b/routes/api-client.php index ec4988bc8b..572f4505b6 100644 --- a/routes/api-client.php +++ b/routes/api-client.php @@ -1,6 +1,7 @@ name('api:client.index'); -Route::get('/permissions', 'ClientController@permissions'); +Route::get('/', [Client\ClientController::class, 'index'])->name('api:client.index'); +Route::get('/permissions', [Client\ClientController::class, 'permissions']); Route::group(['prefix' => '/account'], function () { - Route::get('/', 'AccountController@index')->name('api:client.account')->withoutMiddleware(RequireTwoFactorAuthentication::class); - Route::get('/two-factor', 'TwoFactorController@index')->withoutMiddleware(RequireTwoFactorAuthentication::class); - Route::post('/two-factor', 'TwoFactorController@store')->withoutMiddleware(RequireTwoFactorAuthentication::class); - Route::delete('/two-factor', 'TwoFactorController@delete')->withoutMiddleware(RequireTwoFactorAuthentication::class); + Route::get('/', [Client\AccountController::class, 'index'])->name('api:client.account')->withoutMiddleware(RequireTwoFactorAuthentication::class); + Route::get('/two-factor', [Client\TwoFactorController::class, 'index'])->withoutMiddleware(RequireTwoFactorAuthentication::class); + Route::post('/two-factor', [Client\TwoFactorController::class, 'store'])->withoutMiddleware(RequireTwoFactorAuthentication::class); + Route::delete('/two-factor', [Client\TwoFactorController::class, 'delete'])->withoutMiddleware(RequireTwoFactorAuthentication::class); - Route::put('/email', 'AccountController@updateEmail')->name('api:client.account.update-email'); - Route::put('/password', 'AccountController@updatePassword')->name('api:client.account.update-password'); + Route::put('/email', [Client\AccountController::class, 'updateEmail'])->name('api:client.account.update-email'); + Route::put('/password', [Client\AccountController::class, 'updatePassword'])->name('api:client.account.update-password'); - Route::get('/api-keys', 'ApiKeyController@index'); - Route::post('/api-keys', 'ApiKeyController@store'); - Route::delete('/api-keys/{identifier}', 'ApiKeyController@delete'); + Route::get('/api-keys', [Client\ApiKeyController::class, 'index']); + Route::post('/api-keys', [Client\ApiKeyController::class, 'store']); + Route::delete('/api-keys/{identifier}', [Client\ApiKeyController::class, 'delete']); }); /* @@ -39,83 +40,83 @@ | */ Route::group(['prefix' => '/servers/{server}', 'middleware' => [AuthenticateServerAccess::class, ResourceBelongsToServer::class]], function () { - Route::get('/', 'Servers\ServerController@index')->name('api:client:server.view'); - Route::get('/websocket', 'Servers\WebsocketController')->name('api:client:server.ws'); - Route::get('/resources', 'Servers\ResourceUtilizationController')->name('api:client:server.resources'); + Route::get('/', [Client\Servers\ServerController::class, 'index'])->name('api:client:server.view'); + Route::get('/websocket', Client\Servers\WebsocketController::class)->name('api:client:server.ws'); + Route::get('/resources', Client\Servers\ResourceUtilizationController::class)->name('api:client:server.resources'); - Route::post('/command', 'Servers\CommandController@index'); - Route::post('/power', 'Servers\PowerController@index'); + Route::post('/command', [Client\Servers\CommandController::class, 'index']); + Route::post('/power', [Client\Servers\PowerController::class, 'index']); Route::group(['prefix' => '/databases'], function () { - Route::get('/', 'Servers\DatabaseController@index'); - Route::post('/', 'Servers\DatabaseController@store'); - Route::post('/{database}/rotate-password', 'Servers\DatabaseController@rotatePassword'); - Route::delete('/{database}', 'Servers\DatabaseController@delete'); + Route::get('/', [Client\Servers\DatabaseController::class, 'index']); + Route::post('/', [Client\Servers\DatabaseController::class, 'store']); + Route::post('/{database}/rotate-password', [Client\Servers\DatabaseController::class, 'rotatePassword']); + Route::delete('/{database}', [Client\Servers\DatabaseController::class, 'delete']); }); Route::group(['prefix' => '/files'], function () { - Route::get('/list', 'Servers\FileController@directory'); - Route::get('/contents', 'Servers\FileController@contents'); - Route::get('/download', 'Servers\FileController@download'); - Route::put('/rename', 'Servers\FileController@rename'); - Route::post('/copy', 'Servers\FileController@copy'); - Route::post('/write', 'Servers\FileController@write'); - Route::post('/compress', 'Servers\FileController@compress'); - Route::post('/decompress', 'Servers\FileController@decompress'); - Route::post('/delete', 'Servers\FileController@delete'); - Route::post('/create-folder', 'Servers\FileController@create'); - Route::post('/chmod', 'Servers\FileController@chmod'); - Route::post('/pull', 'Servers\FileController@pull')->middleware(['throttle:10,5']); - Route::get('/upload', 'Servers\FileUploadController'); + Route::get('/list', [Client\Servers\FileController::class, 'directory']); + Route::get('/contents', [Client\Servers\FileController::class, 'contents']); + Route::get('/download', [Client\Servers\FileController::class, 'download']); + Route::put('/rename', [Client\Servers\FileController::class, 'rename']); + Route::post('/copy', [Client\Servers\FileController::class, 'copy']); + Route::post('/write', [Client\Servers\FileController::class, 'write']); + Route::post('/compress', [Client\Servers\FileController::class, 'compress']); + Route::post('/decompress', [Client\Servers\FileController::class, 'decompress']); + Route::post('/delete', [Client\Servers\FileController::class, 'delete']); + Route::post('/create-folder', [Client\Servers\FileController::class, 'create']); + Route::post('/chmod', [Client\Servers\FileController::class, 'chmod']); + Route::post('/pull', [Client\Servers\FileController::class, 'pull'])->middleware(['throttle:10,5']); + Route::get('/upload', Client\Servers\FileUploadController::class); }); Route::group(['prefix' => '/schedules'], function () { - Route::get('/', 'Servers\ScheduleController@index'); - Route::post('/', 'Servers\ScheduleController@store'); - Route::get('/{schedule}', 'Servers\ScheduleController@view'); - Route::post('/{schedule}', 'Servers\ScheduleController@update'); - Route::post('/{schedule}/execute', 'Servers\ScheduleController@execute'); - Route::delete('/{schedule}', 'Servers\ScheduleController@delete'); - - Route::post('/{schedule}/tasks', 'Servers\ScheduleTaskController@store'); - Route::post('/{schedule}/tasks/{task}', 'Servers\ScheduleTaskController@update'); - Route::delete('/{schedule}/tasks/{task}', 'Servers\ScheduleTaskController@delete'); + Route::get('/', [Client\Servers\ScheduleController::class, 'index']); + Route::post('/', [Client\Servers\ScheduleController::class, 'store']); + Route::get('/{schedule}', [Client\Servers\ScheduleController::class, 'view']); + Route::post('/{schedule}', [Client\Servers\ScheduleController::class, 'update']); + Route::post('/{schedule}/execute', [Client\Servers\ScheduleController::class, 'execute']); + Route::delete('/{schedule}', [Client\Servers\ScheduleController::class, 'delete']); + + Route::post('/{schedule}/tasks', [Client\Servers\ScheduleTaskController::class, 'store']); + Route::post('/{schedule}/tasks/{task}', [Client\Servers\ScheduleTaskController::class, 'update']); + Route::delete('/{schedule}/tasks/{task}', [Client\Servers\ScheduleTaskController::class, 'delete']); }); Route::group(['prefix' => '/network'], function () { - Route::get('/allocations', 'Servers\NetworkAllocationController@index'); - Route::post('/allocations', 'Servers\NetworkAllocationController@store'); - Route::post('/allocations/{allocation}', 'Servers\NetworkAllocationController@update'); - Route::post('/allocations/{allocation}/primary', 'Servers\NetworkAllocationController@setPrimary'); - Route::delete('/allocations/{allocation}', 'Servers\NetworkAllocationController@delete'); + Route::get('/allocations', [Client\Servers\NetworkAllocationController::class, 'index']); + Route::post('/allocations', [Client\Servers\NetworkAllocationController::class, 'store']); + Route::post('/allocations/{allocation}', [Client\Servers\NetworkAllocationController::class, 'update']); + Route::post('/allocations/{allocation}/primary', [Client\Servers\NetworkAllocationController::class, 'setPrimary']); + Route::delete('/allocations/{allocation}', [Client\Servers\NetworkAllocationController::class, 'delete']); }); Route::group(['prefix' => '/users'], function () { - Route::get('/', 'Servers\SubuserController@index'); - Route::post('/', 'Servers\SubuserController@store'); - Route::get('/{user}', 'Servers\SubuserController@view'); - Route::post('/{user}', 'Servers\SubuserController@update'); - Route::delete('/{user}', 'Servers\SubuserController@delete'); + Route::get('/', [Client\Servers\SubuserController::class, 'index']); + Route::post('/', [Client\Servers\SubuserController::class, 'store']); + Route::get('/{user}', [Client\Servers\SubuserController::class, 'view']); + Route::post('/{user}', [Client\Servers\SubuserController::class, 'update']); + Route::delete('/{user}', [Client\Servers\SubuserController::class, 'delete']); }); Route::group(['prefix' => '/backups'], function () { - Route::get('/', 'Servers\BackupController@index'); - 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'); + Route::get('/', [Client\Servers\BackupController::class, 'index']); + Route::post('/', [Client\Servers\BackupController::class, 'store']); + Route::get('/{backup}', [Client\Servers\BackupController::class, 'view']); + Route::get('/{backup}/download', [Client\Servers\BackupController::class, 'download']); + Route::post('/{backup}/lock', [Client\Servers\BackupController::class, 'toggleLock']); + Route::post('/{backup}/restore', [Client\Servers\BackupController::class, 'restore']); + Route::delete('/{backup}', [Client\Servers\BackupController::class, 'delete']); }); Route::group(['prefix' => '/startup'], function () { - Route::get('/', 'Servers\StartupController@index'); - Route::put('/variable', 'Servers\StartupController@update'); + Route::get('/', [Client\Servers\StartupController::class, 'index']); + Route::put('/variable', [Client\Servers\StartupController::class, 'update']); }); Route::group(['prefix' => '/settings'], function () { - Route::post('/rename', 'Servers\SettingsController@rename'); - Route::post('/reinstall', 'Servers\SettingsController@reinstall'); - Route::put('/docker-image', 'Servers\SettingsController@dockerImage'); + Route::post('/rename', [Client\Servers\SettingsController::class, 'rename']); + Route::post('/reinstall', [Client\Servers\SettingsController::class, 'reinstall']); + Route::put('/docker-image', [Client\Servers\SettingsController::class, 'dockerImage']); }); }); diff --git a/routes/api-remote.php b/routes/api-remote.php index 3d4839324f..e6ab18825f 100644 --- a/routes/api-remote.php +++ b/routes/api-remote.php @@ -1,25 +1,26 @@ '/servers/{uuid}'], function () { - Route::get('/', 'Servers\ServerDetailsController'); - Route::get('/install', 'Servers\ServerInstallController@index'); - Route::post('/install', 'Servers\ServerInstallController@store'); + Route::get('/', Remote\Servers\ServerDetailsController::class); + Route::get('/install', [Remote\Servers\ServerInstallController::class, 'index']); + Route::post('/install', [Remote\Servers\ServerInstallController::class, 'store']); - Route::post('/archive', 'Servers\ServerTransferController@archive'); - Route::get('/transfer/failure', 'Servers\ServerTransferController@failure'); - Route::get('/transfer/success', 'Servers\ServerTransferController@success'); + Route::post('/archive', [Remote\Servers\ServerTransferController::class, 'archive']); + Route::get('/transfer/failure', [Remote\Servers\ServerTransferController::class, 'failure']); + Route::get('/transfer/success', [Remote\Servers\ServerTransferController::class, 'success']); }); Route::group(['prefix' => '/backups'], function () { - Route::get('/{backup}', 'Backups\BackupRemoteUploadController'); - Route::post('/{backup}', 'Backups\BackupStatusController@index'); - Route::post('/{backup}/restore', 'Backups\BackupStatusController@restore'); + Route::get('/{backup}', Remote\Backups\BackupRemoteUploadController::class); + Route::post('/{backup}', [Remote\Backups\BackupStatusController::class, 'index']); + Route::post('/{backup}/restore', [Remote\Backups\BackupStatusController::class, 'restore']); }); diff --git a/routes/auth.php b/routes/auth.php index 0acd9fded5..7d0930f11d 100644 --- a/routes/auth.php +++ b/routes/auth.php @@ -1,5 +1,7 @@ 'guest'], function () { - // These routes are defined so that we can continue to reference them programatically. - // They all route to the same controller function which passes off to Vuejs. - Route::get('/login', 'LoginController@index')->name('auth.login'); - Route::get('/password', 'LoginController@index')->name('auth.forgot-password'); - Route::get('/password/reset/{token}', 'LoginController@index')->name('auth.reset'); - - // Apply a throttle to authentication action endpoints, in addition to the - // recaptcha endpoints to slow down manual attack spammers even more. 🤷‍ - // - // @see \Pterodactyl\Providers\RouteServiceProvider - Route::middleware(['throttle:authentication'])->group(function () { - // Login endpoints. - Route::post('/login', 'LoginController@login')->middleware('recaptcha'); - Route::post('/login/checkpoint', 'LoginCheckpointController')->name('auth.login-checkpoint'); - - // Forgot password route. A post to this endpoint will trigger an - // email to be sent containing a reset token. - Route::post('/password', 'ForgotPasswordController@sendResetLinkEmail') - ->name('auth.post.forgot-password') - ->middleware('recaptcha'); - }); - - // Password reset routes. This endpoint is hit after going through - // the forgot password routes to acquire a token (or after an account - // is created). - Route::post('/password/reset', 'ResetPasswordController')->name('auth.reset-password'); - - // Catch any other combinations of routes and pass them off to the Vuejs component. - Route::fallback('LoginController@index'); + +// These routes are defined so that we can continue to reference them programatically. +// They all route to the same controller function which passes off to React. +Route::get('/login', [Auth\LoginController::class, 'index'])->name('auth.login'); +Route::get('/password', [Auth\LoginController::class, 'index'])->name('auth.forgot-password'); +Route::get('/password/reset/{token}', [Auth\LoginController::class, 'index'])->name('auth.reset'); + +// Apply a throttle to authentication action endpoints, in addition to the +// recaptcha endpoints to slow down manual attack spammers even more. 🤷‍ +// +// @see \Pterodactyl\Providers\RouteServiceProvider +Route::middleware(['throttle:authentication'])->group(function () { + // Login endpoints. + Route::post('/login', [Auth\LoginController::class, 'login'])->middleware('recaptcha'); + Route::post('/login/checkpoint', Auth\LoginCheckpointController::class)->name('auth.login-checkpoint'); + + // Forgot password route. A post to this endpoint will trigger an + // email to be sent containing a reset token. + Route::post('/password', [Auth\ForgotPasswordController::class, 'sendResetLinkEmail']) + ->name('auth.post.forgot-password') + ->middleware('recaptcha'); }); -/* -|-------------------------------------------------------------------------- -| Routes Accessible only when logged in -|-------------------------------------------------------------------------- -| -| Endpoint: /auth -| -*/ -Route::post('/logout', 'LoginController@logout')->name('auth.logout')->middleware('auth', 'csrf'); +// Password reset routes. This endpoint is hit after going through +// the forgot password routes to acquire a token (or after an account +// is created). +Route::post('/password/reset', Auth\ResetPasswordController::class)->name('auth.reset-password'); + +// Remove the guest middleware and apply the authenticated middleware to this endpoint +// so it cannot be used unless you're already logged in. +Route::post('/logout', [Auth\LoginController::class, 'logout']) + ->withoutMiddleware('guest') + ->middleware('auth') + ->name('auth.logout'); + +// Catch any other combinations of routes and pass them off to the Vuejs component. +Route::fallback([Auth\LoginController::class, 'index']); diff --git a/routes/base.php b/routes/base.php index 3be62423e5..8ca1fbc3a9 100644 --- a/routes/base.php +++ b/routes/base.php @@ -1,15 +1,16 @@ name('index')->fallback(); -Route::get('/account', 'IndexController@index') +Route::get('/', [Base\IndexController::class, 'index'])->name('index')->fallback(); +Route::get('/account', [Base\IndexController::class, 'index']) ->withoutMiddleware(RequireTwoFactorAuthentication::class) ->name('account'); -Route::get('/locales/{locale}/{namespace}.json', 'LocaleController') - ->withoutMiddleware(RequireTwoFactorAuthentication::class) +Route::get('/locales/{locale}/{namespace}.json', Base\LocaleController::class) + ->withoutMiddleware(['auth', RequireTwoFactorAuthentication::class]) ->where('namespace', '.*'); -Route::get('/{react}', 'IndexController@index') +Route::get('/{react}', [Base\IndexController::class, 'index']) ->where('react', '^(?!(\/)?(api|auth|admin|daemon)).+'); diff --git a/routes/server.php b/routes/server.php deleted file mode 100644 index fb8c124996..0000000000 --- a/routes/server.php +++ /dev/null @@ -1,10 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ -Route::get('/')->name('server.index'); -Route::get('/console')->name('server.console'); From 5705d7dbddc3f59b8c39baee2759592bf0ef8c90 Mon Sep 17 00:00:00 2001 From: DaneEveritt Date: Sat, 14 May 2022 16:03:50 -0400 Subject: [PATCH 028/458] Run php-cs-fixer --- app/Http/Kernel.php | 2 +- app/Models/Model.php | 2 +- app/Transformers/Api/Application/AllocationTransformer.php | 2 -- .../Api/Application/DatabaseHostTransformer.php | 3 --- app/Transformers/Api/Application/EggTransformer.php | 4 +--- app/Transformers/Api/Application/LocationTransformer.php | 2 -- app/Transformers/Api/Application/NestTransformer.php | 2 -- app/Transformers/Api/Application/NodeTransformer.php | 2 -- .../Api/Application/ServerDatabaseTransformer.php | 3 --- app/Transformers/Api/Application/ServerTransformer.php | 2 -- .../Api/Application/ServerVariableTransformer.php | 2 -- app/Transformers/Api/Application/SubuserTransformer.php | 2 -- app/Transformers/Api/Application/UserTransformer.php | 2 -- app/Transformers/Api/Client/ScheduleTransformer.php | 6 ------ app/Transformers/Api/Client/ServerTransformer.php | 3 --- .../Integration/Api/Client/ClientApiIntegrationTestCase.php | 1 + 16 files changed, 4 insertions(+), 36 deletions(-) diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index ea4346aefd..2ff7a6260a 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -4,8 +4,8 @@ use Pterodactyl\Models\ApiKey; use Illuminate\Auth\Middleware\Authorize; -use Illuminate\Http\Middleware\TrustProxies; use Illuminate\Auth\Middleware\Authenticate; +use Illuminate\Http\Middleware\TrustProxies; use Pterodactyl\Http\Middleware\TrimStrings; use Illuminate\Session\Middleware\StartSession; use Pterodactyl\Http\Middleware\EncryptCookies; diff --git a/app/Models/Model.php b/app/Models/Model.php index 5eaa1fa68b..3fca705fd2 100644 --- a/app/Models/Model.php +++ b/app/Models/Model.php @@ -2,8 +2,8 @@ namespace Pterodactyl\Models; -use Illuminate\Support\Str; use Illuminate\Support\Arr; +use Illuminate\Support\Str; use Illuminate\Validation\Rule; use Illuminate\Container\Container; use Illuminate\Contracts\Validation\Factory; diff --git a/app/Transformers/Api/Application/AllocationTransformer.php b/app/Transformers/Api/Application/AllocationTransformer.php index cc749e194c..3584d86835 100644 --- a/app/Transformers/Api/Application/AllocationTransformer.php +++ b/app/Transformers/Api/Application/AllocationTransformer.php @@ -11,8 +11,6 @@ class AllocationTransformer extends BaseTransformer { /** * Relationships that can be loaded onto allocation transformations. - * - * @var array */ protected array $availableIncludes = ['node', 'server']; diff --git a/app/Transformers/Api/Application/DatabaseHostTransformer.php b/app/Transformers/Api/Application/DatabaseHostTransformer.php index 3855cf3722..08d635a033 100644 --- a/app/Transformers/Api/Application/DatabaseHostTransformer.php +++ b/app/Transformers/Api/Application/DatabaseHostTransformer.php @@ -8,9 +8,6 @@ class DatabaseHostTransformer extends BaseTransformer { - /** - * @var array - */ protected array $availableIncludes = [ 'databases', ]; diff --git a/app/Transformers/Api/Application/EggTransformer.php b/app/Transformers/Api/Application/EggTransformer.php index d2b269b73c..8a0dbf8b38 100644 --- a/app/Transformers/Api/Application/EggTransformer.php +++ b/app/Transformers/Api/Application/EggTransformer.php @@ -2,8 +2,8 @@ namespace Pterodactyl\Transformers\Api\Application; -use Pterodactyl\Models\Egg; use Illuminate\Support\Arr; +use Pterodactyl\Models\Egg; use Pterodactyl\Models\Nest; use Pterodactyl\Models\Server; use Pterodactyl\Models\EggVariable; @@ -13,8 +13,6 @@ class EggTransformer extends BaseTransformer { /** * Relationships that can be loaded onto this transformation. - * - * @var array */ protected array $availableIncludes = [ 'nest', diff --git a/app/Transformers/Api/Application/LocationTransformer.php b/app/Transformers/Api/Application/LocationTransformer.php index 4276677340..90564d81df 100644 --- a/app/Transformers/Api/Application/LocationTransformer.php +++ b/app/Transformers/Api/Application/LocationTransformer.php @@ -9,8 +9,6 @@ class LocationTransformer extends BaseTransformer { /** * List of resources that can be included. - * - * @var array */ protected array $availableIncludes = ['nodes', 'servers']; diff --git a/app/Transformers/Api/Application/NestTransformer.php b/app/Transformers/Api/Application/NestTransformer.php index 29f9559ae8..1274901a7c 100644 --- a/app/Transformers/Api/Application/NestTransformer.php +++ b/app/Transformers/Api/Application/NestTransformer.php @@ -11,8 +11,6 @@ class NestTransformer extends BaseTransformer { /** * Relationships that can be loaded onto this transformation. - * - * @var array */ protected array $availableIncludes = [ 'eggs', 'servers', diff --git a/app/Transformers/Api/Application/NodeTransformer.php b/app/Transformers/Api/Application/NodeTransformer.php index fc7d9c0f06..c7c1fc93b5 100644 --- a/app/Transformers/Api/Application/NodeTransformer.php +++ b/app/Transformers/Api/Application/NodeTransformer.php @@ -9,8 +9,6 @@ class NodeTransformer extends BaseTransformer { /** * List of resources that can be included. - * - * @var array */ protected array $availableIncludes = ['allocations', 'location', 'servers']; diff --git a/app/Transformers/Api/Application/ServerDatabaseTransformer.php b/app/Transformers/Api/Application/ServerDatabaseTransformer.php index 58de97a63e..4e425c9e4c 100644 --- a/app/Transformers/Api/Application/ServerDatabaseTransformer.php +++ b/app/Transformers/Api/Application/ServerDatabaseTransformer.php @@ -9,9 +9,6 @@ class ServerDatabaseTransformer extends BaseTransformer { - /** - * @var array - */ protected array $availableIncludes = ['password', 'host']; /** diff --git a/app/Transformers/Api/Application/ServerTransformer.php b/app/Transformers/Api/Application/ServerTransformer.php index b96c0aa6bd..33110ec7eb 100644 --- a/app/Transformers/Api/Application/ServerTransformer.php +++ b/app/Transformers/Api/Application/ServerTransformer.php @@ -15,8 +15,6 @@ class ServerTransformer extends BaseTransformer /** * List of resources that can be included. - * - * @var array */ protected array $availableIncludes = [ 'allocations', diff --git a/app/Transformers/Api/Application/ServerVariableTransformer.php b/app/Transformers/Api/Application/ServerVariableTransformer.php index 28d0650480..2b1d68876d 100644 --- a/app/Transformers/Api/Application/ServerVariableTransformer.php +++ b/app/Transformers/Api/Application/ServerVariableTransformer.php @@ -9,8 +9,6 @@ class ServerVariableTransformer extends BaseTransformer { /** * List of resources that can be included. - * - * @var array */ protected array $availableIncludes = ['parent']; diff --git a/app/Transformers/Api/Application/SubuserTransformer.php b/app/Transformers/Api/Application/SubuserTransformer.php index 7ce9eba3c4..c7b8abd9e4 100644 --- a/app/Transformers/Api/Application/SubuserTransformer.php +++ b/app/Transformers/Api/Application/SubuserTransformer.php @@ -9,8 +9,6 @@ class SubuserTransformer extends BaseTransformer { /** * List of resources that can be included. - * - * @var array */ protected array $availableIncludes = ['user', 'server']; diff --git a/app/Transformers/Api/Application/UserTransformer.php b/app/Transformers/Api/Application/UserTransformer.php index 8202512fe0..9e2769a13a 100644 --- a/app/Transformers/Api/Application/UserTransformer.php +++ b/app/Transformers/Api/Application/UserTransformer.php @@ -9,8 +9,6 @@ class UserTransformer extends BaseTransformer { /** * List of resources that can be included. - * - * @var array */ protected array $availableIncludes = ['servers']; diff --git a/app/Transformers/Api/Client/ScheduleTransformer.php b/app/Transformers/Api/Client/ScheduleTransformer.php index 3f42234b2a..07dbf77cbc 100644 --- a/app/Transformers/Api/Client/ScheduleTransformer.php +++ b/app/Transformers/Api/Client/ScheduleTransformer.php @@ -8,14 +8,8 @@ class ScheduleTransformer extends BaseClientTransformer { - /** - * @var array - */ protected array $availableIncludes = ['tasks']; - /** - * @var array - */ protected array $defaultIncludes = ['tasks']; /** diff --git a/app/Transformers/Api/Client/ServerTransformer.php b/app/Transformers/Api/Client/ServerTransformer.php index 1e4412b915..583ccab686 100644 --- a/app/Transformers/Api/Client/ServerTransformer.php +++ b/app/Transformers/Api/Client/ServerTransformer.php @@ -18,9 +18,6 @@ class ServerTransformer extends BaseClientTransformer */ protected array $defaultIncludes = ['allocations', 'variables']; - /** - * @var array - */ protected array $availableIncludes = ['egg', 'subusers']; public function getResourceName(): string diff --git a/tests/Integration/Api/Client/ClientApiIntegrationTestCase.php b/tests/Integration/Api/Client/ClientApiIntegrationTestCase.php index 22f941807b..33f6069a5d 100644 --- a/tests/Integration/Api/Client/ClientApiIntegrationTestCase.php +++ b/tests/Integration/Api/Client/ClientApiIntegrationTestCase.php @@ -89,6 +89,7 @@ protected function link($model, $append = null): string * is assumed that the user is actually a subuser of the server. * * @param string[] $permissions + * * @return array{\Pterodactyl\Models\User, \Pterodactyl\Models\Server} */ protected function generateTestAccount(array $permissions = []): array From 97280a62a2e526b5194b865d1d991ef46f92f2b1 Mon Sep 17 00:00:00 2001 From: DaneEveritt Date: Sat, 14 May 2022 17:31:53 -0400 Subject: [PATCH 029/458] Add support for storing SSH keys on user accounts --- .../Api/Client/SSHKeyController.php | 48 ++++++++ .../Api/Application/ApplicationApiRequest.php | 11 ++ .../Api/Client/Account/StoreSSHKeyRequest.php | 71 +++++++++++ app/Models/User.php | 39 ++++++ app/Models/UserSSHKey.php | 61 ++++++++++ .../Api/Client/SSHKeyTransformer.php | 26 ++++ composer.json | 1 + composer.lock | 111 +++++++++++++++++- ...7_17_211512_create_user_ssh_keys_table.php | 34 ++++++ resources/scripts/api/account/ssh-keys.ts | 28 +++++ .../scripts/api/definitions/user/models.d.ts | 10 +- .../api/definitions/user/transformers.ts | 10 ++ .../dashboard/ssh/AccountSSHContainer.tsx | 66 +++++++++++ .../dashboard/ssh/CreateSSHKeyForm.tsx | 67 +++++++++++ .../dashboard/ssh/DeleteSSHKeyButton.tsx | 46 ++++++++ resources/scripts/plugins/useFlash.ts | 15 +++ .../scripts/plugins/useUserSWRContentKey.ts | 12 ++ resources/scripts/routers/DashboardRouter.tsx | 5 + resources/scripts/state/flashes.ts | 17 ++- routes/api-client.php | 6 + 20 files changed, 678 insertions(+), 6 deletions(-) create mode 100644 app/Http/Controllers/Api/Client/SSHKeyController.php create mode 100644 app/Http/Requests/Api/Client/Account/StoreSSHKeyRequest.php create mode 100644 app/Models/UserSSHKey.php create mode 100644 app/Transformers/Api/Client/SSHKeyTransformer.php create mode 100644 database/migrations/2021_07_17_211512_create_user_ssh_keys_table.php create mode 100644 resources/scripts/api/account/ssh-keys.ts create mode 100644 resources/scripts/components/dashboard/ssh/AccountSSHContainer.tsx create mode 100644 resources/scripts/components/dashboard/ssh/CreateSSHKeyForm.tsx create mode 100644 resources/scripts/components/dashboard/ssh/DeleteSSHKeyButton.tsx create mode 100644 resources/scripts/plugins/useUserSWRContentKey.ts diff --git a/app/Http/Controllers/Api/Client/SSHKeyController.php b/app/Http/Controllers/Api/Client/SSHKeyController.php new file mode 100644 index 0000000000..5b25a221cc --- /dev/null +++ b/app/Http/Controllers/Api/Client/SSHKeyController.php @@ -0,0 +1,48 @@ +fractal->collection($request->user()->sshKeys) + ->transformWith($this->getTransformer(SSHKeyTransformer::class)) + ->toArray(); + } + + /** + * Stores a new SSH key for the authenticated user's account. + */ + public function store(StoreSSHKeyRequest $request): array + { + $model = $request->user()->sshKeys()->create([ + 'name' => $request->input('name'), + 'public_key' => $request->input('public_key'), + 'fingerprint' => $request->getKeyFingerprint(), + ]); + + return $this->fractal->item($model) + ->transformWith($this->getTransformer(SSHKeyTransformer::class)) + ->toArray(); + } + + /** + * Deletes an SSH key from the user's account. + */ + public function delete(ClientApiRequest $request, string $identifier): JsonResponse + { + $request->user()->sshKeys()->where('fingerprint', $identifier)->delete(); + + return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT); + } +} diff --git a/app/Http/Requests/Api/Application/ApplicationApiRequest.php b/app/Http/Requests/Api/Application/ApplicationApiRequest.php index 11deab45b5..064a368533 100644 --- a/app/Http/Requests/Api/Application/ApplicationApiRequest.php +++ b/app/Http/Requests/Api/Application/ApplicationApiRequest.php @@ -3,6 +3,7 @@ namespace Pterodactyl\Http\Requests\Api\Application; use Pterodactyl\Models\ApiKey; +use Illuminate\Validation\Validator; use Pterodactyl\Services\Acl\Api\AdminAcl; use Illuminate\Foundation\Http\FormRequest; use Pterodactyl\Exceptions\PterodactylException; @@ -96,6 +97,16 @@ public function getModel(string $model) return $this->route()->parameter($parameterKey); } + /** + * Helper method allowing a developer to easily hook into this logic without having + * to remember what the method name is called or where to use it. By default this is + * a no-op. + */ + public function withValidator(Validator $validator): void + { + // do nothing + } + /** * Validate that the resource exists and can be accessed prior to booting * the validator and attempting to use the data. diff --git a/app/Http/Requests/Api/Client/Account/StoreSSHKeyRequest.php b/app/Http/Requests/Api/Client/Account/StoreSSHKeyRequest.php new file mode 100644 index 0000000000..5705056c48 --- /dev/null +++ b/app/Http/Requests/Api/Client/Account/StoreSSHKeyRequest.php @@ -0,0 +1,71 @@ + UserSSHKey::getRulesForField('name'), + 'public_key' => UserSSHKey::getRulesForField('public_key'), + ]; + } + + /** + * Check to see if this SSH key has already been added to the user's account + * and if so return an error. + */ + public function withValidator(Validator $validator): void + { + $validator->after(function () { + try { + $this->key = PublicKeyLoader::loadPublicKey($this->input('public_key')); + } catch (NoKeyLoadedException $exception) { + $this->validator->errors()->add('public_key', 'The public key provided is not valid.'); + + return; + } + + if ($this->key instanceof DSA) { + $this->validator->errors()->add('public_key', 'DSA public keys are not supported.'); + } + + if ($this->key instanceof RSA && $this->key->getLength() < 2048) { + $this->validator->errors()->add('public_key', 'RSA keys must be at 2048 bytes.'); + } + + $fingerprint = $this->key->getFingerprint('sha256'); + if ($this->user()->sshKeys()->where('fingerprint', $fingerprint)->exists()) { + $this->validator->errors()->add('public_key', 'The public key provided already exists on your account.'); + } + }); + } + + /** + * Returns the SHA256 fingerprint of the key provided. + */ + public function getKeyFingerprint(): string + { + if (!$this->key) { + throw new Exception('The public key was not properly loaded for this request.'); + } + + return $this->key->getFingerprint('sha256'); + } +} diff --git a/app/Models/User.php b/app/Models/User.php index 6cdc414f25..42ddc774ec 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -10,6 +10,7 @@ use Illuminate\Database\Eloquent\Builder; use Illuminate\Auth\Passwords\CanResetPassword; use Pterodactyl\Traits\Helpers\AvailableLanguages; +use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Foundation\Auth\Access\Authorizable; use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract; use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract; @@ -17,6 +18,8 @@ use Pterodactyl\Notifications\SendPasswordReset as ResetPasswordNotification; /** + * \Pterodactyl\Models\User. + * * @property int $id * @property string|null $external_id * @property string $uuid @@ -38,6 +41,37 @@ * @property \Pterodactyl\Models\ApiKey[]|\Illuminate\Database\Eloquent\Collection $apiKeys * @property \Pterodactyl\Models\Server[]|\Illuminate\Database\Eloquent\Collection $servers * @property \Pterodactyl\Models\RecoveryToken[]|\Illuminate\Database\Eloquent\Collection $recoveryTokens + * @property string|null $remember_token + * @property int|null $api_keys_count + * @property \Illuminate\Notifications\DatabaseNotificationCollection|\Illuminate\Notifications\DatabaseNotification[] $notifications + * @property int|null $notifications_count + * @property int|null $recovery_tokens_count + * @property int|null $servers_count + * @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\UserSSHKey[] $sshKeys + * @property int|null $ssh_keys_count + * + * @method static \Database\Factories\UserFactory factory(...$parameters) + * @method static Builder|User newModelQuery() + * @method static Builder|User newQuery() + * @method static Builder|User query() + * @method static Builder|User whereCreatedAt($value) + * @method static Builder|User whereEmail($value) + * @method static Builder|User whereExternalId($value) + * @method static Builder|User whereGravatar($value) + * @method static Builder|User whereId($value) + * @method static Builder|User whereLanguage($value) + * @method static Builder|User whereNameFirst($value) + * @method static Builder|User whereNameLast($value) + * @method static Builder|User wherePassword($value) + * @method static Builder|User whereRememberToken($value) + * @method static Builder|User whereRootAdmin($value) + * @method static Builder|User whereTotpAuthenticatedAt($value) + * @method static Builder|User whereTotpSecret($value) + * @method static Builder|User whereUpdatedAt($value) + * @method static Builder|User whereUseTotp($value) + * @method static Builder|User whereUsername($value) + * @method static Builder|User whereUuid($value) + * @mixin \Eloquent */ class User extends Model implements AuthenticatableContract, @@ -225,6 +259,11 @@ public function recoveryTokens() return $this->hasMany(RecoveryToken::class); } + public function sshKeys(): HasMany + { + return $this->hasMany(UserSSHKey::class); + } + /** * Returns all of the servers that a user can access by way of being the owner of the * server, or because they are assigned as a subuser for that server. diff --git a/app/Models/UserSSHKey.php b/app/Models/UserSSHKey.php new file mode 100644 index 0000000000..848ba32c89 --- /dev/null +++ b/app/Models/UserSSHKey.php @@ -0,0 +1,61 @@ + ['required', 'string'], + 'fingerprint' => ['required', 'string'], + 'public_key' => ['required', 'string'], + ]; + + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } +} diff --git a/app/Transformers/Api/Client/SSHKeyTransformer.php b/app/Transformers/Api/Client/SSHKeyTransformer.php new file mode 100644 index 0000000000..1f9b96c290 --- /dev/null +++ b/app/Transformers/Api/Client/SSHKeyTransformer.php @@ -0,0 +1,26 @@ + $model->name, + 'fingerprint' => $model->fingerprint, + 'public_key' => $model->public_key, + 'created_at' => $model->created_at->toIso8601String(), + ]; + } +} diff --git a/composer.json b/composer.json index fa5db6eb0c..8279aee77b 100644 --- a/composer.json +++ b/composer.json @@ -30,6 +30,7 @@ "league/flysystem-aws-s3-v3": "~1.0.29", "league/flysystem-memory": "~1.0.2", "matriphe/iso-639": "~1.2.0", + "phpseclib/phpseclib": "~3.0", "pragmarx/google2fa": "~5.0.0", "predis/predis": "~1.1.10", "prologue/alerts": "~0.4.8", diff --git a/composer.lock b/composer.lock index 8fecedfe99..db2b52ca1c 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "966e12710f76fb744c32e90103b9f823", + "content-hash": "59024efe671be95afe14319b19606566", "packages": [ { "name": "aws/aws-crt-php", @@ -3266,6 +3266,115 @@ ], "time": "2021-12-04T23:24:31+00:00" }, + { + "name": "phpseclib/phpseclib", + "version": "3.0.14", + "source": { + "type": "git", + "url": "https://github.com/phpseclib/phpseclib.git", + "reference": "2f0b7af658cbea265cbb4a791d6c29a6613f98ef" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/2f0b7af658cbea265cbb4a791d6c29a6613f98ef", + "reference": "2f0b7af658cbea265cbb4a791d6c29a6613f98ef", + "shasum": "" + }, + "require": { + "paragonie/constant_time_encoding": "^1|^2", + "paragonie/random_compat": "^1.4|^2.0|^9.99.99", + "php": ">=5.6.1" + }, + "require-dev": { + "phpunit/phpunit": "*" + }, + "suggest": { + "ext-gmp": "Install the GMP (GNU Multiple Precision) extension in order to speed up arbitrary precision integer arithmetic operations.", + "ext-libsodium": "SSH2/SFTP can make use of some algorithms provided by the libsodium-php extension.", + "ext-mcrypt": "Install the Mcrypt extension in order to speed up a few other cryptographic operations.", + "ext-openssl": "Install the OpenSSL extension in order to speed up a wide variety of cryptographic operations." + }, + "type": "library", + "autoload": { + "files": [ + "phpseclib/bootstrap.php" + ], + "psr-4": { + "phpseclib3\\": "phpseclib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jim Wigginton", + "email": "terrafrost@php.net", + "role": "Lead Developer" + }, + { + "name": "Patrick Monnerat", + "email": "pm@datasphere.ch", + "role": "Developer" + }, + { + "name": "Andreas Fischer", + "email": "bantu@phpbb.com", + "role": "Developer" + }, + { + "name": "Hans-Jürgen Petrich", + "email": "petrich@tronic-media.com", + "role": "Developer" + }, + { + "name": "Graham Campbell", + "email": "graham@alt-three.com", + "role": "Developer" + } + ], + "description": "PHP Secure Communications Library - Pure-PHP implementations of RSA, AES, SSH2, SFTP, X.509 etc.", + "homepage": "http://phpseclib.sourceforge.net", + "keywords": [ + "BigInteger", + "aes", + "asn.1", + "asn1", + "blowfish", + "crypto", + "cryptography", + "encryption", + "rsa", + "security", + "sftp", + "signature", + "signing", + "ssh", + "twofish", + "x.509", + "x509" + ], + "support": { + "issues": "https://github.com/phpseclib/phpseclib/issues", + "source": "https://github.com/phpseclib/phpseclib/tree/3.0.14" + }, + "funding": [ + { + "url": "https://github.com/terrafrost", + "type": "github" + }, + { + "url": "https://www.patreon.com/phpseclib", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpseclib/phpseclib", + "type": "tidelift" + } + ], + "time": "2022-04-04T05:15:45+00:00" + }, { "name": "pragmarx/google2fa", "version": "v5.0.0", diff --git a/database/migrations/2021_07_17_211512_create_user_ssh_keys_table.php b/database/migrations/2021_07_17_211512_create_user_ssh_keys_table.php new file mode 100644 index 0000000000..d5b8a13c6a --- /dev/null +++ b/database/migrations/2021_07_17_211512_create_user_ssh_keys_table.php @@ -0,0 +1,34 @@ +increments('id'); + $table->unsignedInteger('user_id'); + $table->string('name'); + $table->string('fingerprint'); + $table->text('public_key'); + $table->timestamps(); + $table->softDeletes(); + + $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); + }); + } + + /** + * Reverse the migrations. + */ + public function down() + { + Schema::dropIfExists('user_ssh_keys'); + } +} diff --git a/resources/scripts/api/account/ssh-keys.ts b/resources/scripts/api/account/ssh-keys.ts new file mode 100644 index 0000000000..baf3b609c8 --- /dev/null +++ b/resources/scripts/api/account/ssh-keys.ts @@ -0,0 +1,28 @@ +import useSWR, { ConfigInterface } from 'swr'; +import useUserSWRContentKey from '@/plugins/useUserSWRContentKey'; +import http, { FractalResponseList } from '@/api/http'; +import { SSHKey, Transformers } from '@definitions/user'; +import { AxiosError } from 'axios'; + +const useSSHKeys = (config?: ConfigInterface) => { + const key = useUserSWRContentKey([ 'account', 'ssh-keys' ]); + + return useSWR(key, async () => { + const { data } = await http.get('/api/client/account/ssh-keys'); + + return (data as FractalResponseList).data.map((datum: any) => { + return Transformers.toSSHKey(datum.attributes); + }); + }, { revalidateOnMount: false, ...(config || {}) }); +}; + +const createSSHKey = async (name: string, publicKey: string): Promise => { + const { data } = await http.post('/api/client/account/ssh-keys', { name, public_key: publicKey }); + + return Transformers.toSSHKey(data.attributes); +}; + +const deleteSSHKey = async (fingerprint: string): Promise => + await http.delete(`/api/client/account/ssh-keys/${fingerprint}`); + +export { useSSHKeys, createSSHKey, deleteSSHKey }; diff --git a/resources/scripts/api/definitions/user/models.d.ts b/resources/scripts/api/definitions/user/models.d.ts index d462cadf8a..51bea475c7 100644 --- a/resources/scripts/api/definitions/user/models.d.ts +++ b/resources/scripts/api/definitions/user/models.d.ts @@ -1,2 +1,8 @@ -// empty export -export type _T = string; +import { Model } from '@/api/definitions'; + +interface SSHKey extends Model { + name: string; + publicKey: string; + fingerprint: string; + createdAt: Date; +} diff --git a/resources/scripts/api/definitions/user/transformers.ts b/resources/scripts/api/definitions/user/transformers.ts index a69ad708d7..89adbad754 100644 --- a/resources/scripts/api/definitions/user/transformers.ts +++ b/resources/scripts/api/definitions/user/transformers.ts @@ -1,4 +1,14 @@ +import { SSHKey } from '@definitions/user/models'; + export default class Transformers { + static toSSHKey (data: Record): SSHKey { + return { + name: data.name, + publicKey: data.public_key, + fingerprint: data.fingerprint, + createdAt: new Date(data.created_at), + }; + } } export class MetaTransformers { diff --git a/resources/scripts/components/dashboard/ssh/AccountSSHContainer.tsx b/resources/scripts/components/dashboard/ssh/AccountSSHContainer.tsx new file mode 100644 index 0000000000..59539b3cef --- /dev/null +++ b/resources/scripts/components/dashboard/ssh/AccountSSHContainer.tsx @@ -0,0 +1,66 @@ +import React, { useEffect } from 'react'; +import ContentBox from '@/components/elements/ContentBox'; +import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; +import FlashMessageRender from '@/components/FlashMessageRender'; +import PageContentBlock from '@/components/elements/PageContentBlock'; +import tw from 'twin.macro'; +import GreyRowBox from '@/components/elements/GreyRowBox'; +import { useSSHKeys } from '@/api/account/ssh-keys'; +import { useFlashKey } from '@/plugins/useFlash'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faKey } from '@fortawesome/free-solid-svg-icons'; +import { format } from 'date-fns'; +import CreateSSHKeyForm from '@/components/dashboard/ssh/CreateSSHKeyForm'; +import DeleteSSHKeyButton from '@/components/dashboard/ssh/DeleteSSHKeyButton'; + +export default () => { + const { clearAndAddHttpError } = useFlashKey('account'); + const { data, isValidating, error } = useSSHKeys({ + revalidateOnMount: true, + revalidateOnFocus: false, + }); + + useEffect(() => { + clearAndAddHttpError(error); + }, [ error ]); + + return ( + + +
+ + + + + + { + !data || !data.length ? +

+ {!data ? 'Loading...' : 'No SSH Keys exist for this account.'} +

+ : + data.map((key, index) => ( + 0 && tw`mt-2` ]} + > + +
+

{key.name}

+

+ SHA256:{key.fingerprint} +

+

+ Added on:  + {format(key.createdAt, 'MMM do, yyyy HH:mm')} +

+
+ +
+ )) + } +
+
+
+ ); +}; diff --git a/resources/scripts/components/dashboard/ssh/CreateSSHKeyForm.tsx b/resources/scripts/components/dashboard/ssh/CreateSSHKeyForm.tsx new file mode 100644 index 0000000000..0443901b80 --- /dev/null +++ b/resources/scripts/components/dashboard/ssh/CreateSSHKeyForm.tsx @@ -0,0 +1,67 @@ +import React from 'react'; +import { Field, Form, Formik, FormikHelpers } from 'formik'; +import { object, string } from 'yup'; +import FormikFieldWrapper from '@/components/elements/FormikFieldWrapper'; +import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; +import tw from 'twin.macro'; +import Button from '@/components/elements/Button'; +import Input, { Textarea } from '@/components/elements/Input'; +import styled from 'styled-components/macro'; +import { useFlashKey } from '@/plugins/useFlash'; +import { createSSHKey, useSSHKeys } from '@/api/account/ssh-keys'; + +interface Values { + name: string; + publicKey: string; +} + +const CustomTextarea = styled(Textarea)`${tw`h-32`}`; + +export default () => { + const { clearAndAddHttpError } = useFlashKey('account'); + const { mutate } = useSSHKeys(); + + const submit = (values: Values, { setSubmitting, resetForm }: FormikHelpers) => { + clearAndAddHttpError(); + + createSSHKey(values.name, values.publicKey) + .then((key) => { + resetForm(); + mutate((data) => (data || []).concat(key)); + }) + .catch((error) => clearAndAddHttpError(error)) + .then(() => setSubmitting(false)); + }; + + return ( + <> + + {({ isSubmitting }) => ( +
+ + + + + + + +
+ +
+ + )} +
+ + ); +}; diff --git a/resources/scripts/components/dashboard/ssh/DeleteSSHKeyButton.tsx b/resources/scripts/components/dashboard/ssh/DeleteSSHKeyButton.tsx new file mode 100644 index 0000000000..f5c31e3223 --- /dev/null +++ b/resources/scripts/components/dashboard/ssh/DeleteSSHKeyButton.tsx @@ -0,0 +1,46 @@ +import tw from 'twin.macro'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faTrashAlt } from '@fortawesome/free-solid-svg-icons'; +import React, { useState } from 'react'; +import { useFlashKey } from '@/plugins/useFlash'; +import ConfirmationModal from '@/components/elements/ConfirmationModal'; +import { deleteSSHKey, useSSHKeys } from '@/api/account/ssh-keys'; + +export default ({ fingerprint }: { fingerprint: string }) => { + const { clearAndAddHttpError } = useFlashKey('account'); + const [ visible, setVisible ] = useState(false); + const { mutate } = useSSHKeys(); + + const onClick = () => { + clearAndAddHttpError(); + + Promise.all([ + mutate((data) => data?.filter((value) => value.fingerprint !== fingerprint), false), + deleteSSHKey(fingerprint), + ]) + .catch((error) => { + mutate(undefined, true); + clearAndAddHttpError(error); + }); + }; + + return ( + <> + setVisible(false)} + > + Are you sure you wish to delete this SSH key? + + + + ); +}; diff --git a/resources/scripts/plugins/useFlash.ts b/resources/scripts/plugins/useFlash.ts index a55b87312d..830be92518 100644 --- a/resources/scripts/plugins/useFlash.ts +++ b/resources/scripts/plugins/useFlash.ts @@ -2,8 +2,23 @@ import { Actions, useStoreActions } from 'easy-peasy'; import { FlashStore } from '@/state/flashes'; import { ApplicationStore } from '@/state'; +interface KeyedFlashStore { + clearFlashes: () => void; + clearAndAddHttpError: (error?: Error | string | null) => void; +} + const useFlash = (): Actions => { return useStoreActions((actions: Actions) => actions.flashes); }; +const useFlashKey = (key: string): KeyedFlashStore => { + const { clearFlashes, clearAndAddHttpError } = useFlash(); + + return { + clearFlashes: () => clearFlashes(key), + clearAndAddHttpError: (error) => clearAndAddHttpError({ key, error }), + }; +}; + +export { useFlashKey }; export default useFlash; diff --git a/resources/scripts/plugins/useUserSWRContentKey.ts b/resources/scripts/plugins/useUserSWRContentKey.ts new file mode 100644 index 0000000000..23ea2eca2c --- /dev/null +++ b/resources/scripts/plugins/useUserSWRContentKey.ts @@ -0,0 +1,12 @@ +import { useStoreState } from '@/state/hooks'; + +export default (context: string | string[]) => { + const key = Array.isArray(context) ? context.join(':') : context; + const uuid = useStoreState(state => state.user.data?.uuid); + + if (!key.trim().length) { + throw new Error('Must provide a valid context key to "useUserSWRContextKey".'); + } + + return `swr::${uuid || 'unknown'}:${key.trim()}`; +}; diff --git a/resources/scripts/routers/DashboardRouter.tsx b/resources/scripts/routers/DashboardRouter.tsx index 513cc3fa84..36bc5b40ee 100644 --- a/resources/scripts/routers/DashboardRouter.tsx +++ b/resources/scripts/routers/DashboardRouter.tsx @@ -7,6 +7,7 @@ import AccountApiContainer from '@/components/dashboard/AccountApiContainer'; import { NotFound } from '@/components/elements/ScreenBlock'; import TransitionRouter from '@/TransitionRouter'; import SubNavigation from '@/components/elements/SubNavigation'; +import AccountSSHContainer from '@/components/dashboard/ssh/AccountSSHContainer'; export default ({ location }: RouteComponentProps) => ( <> @@ -16,6 +17,7 @@ export default ({ location }: RouteComponentProps) => (
Settings API Credentials + SSH Keys
} @@ -30,6 +32,9 @@ export default ({ location }: RouteComponentProps) => ( + + + diff --git a/resources/scripts/state/flashes.ts b/resources/scripts/state/flashes.ts index fb89a0a8dd..31ab75a361 100644 --- a/resources/scripts/state/flashes.ts +++ b/resources/scripts/state/flashes.ts @@ -6,7 +6,7 @@ export interface FlashStore { items: FlashMessage[]; addFlash: Action; addError: Action; - clearAndAddHttpError: Action; + clearAndAddHttpError: Action; clearFlashes: Action; } @@ -29,8 +29,19 @@ const flashes: FlashStore = { state.items.push({ type: 'error', title: 'Error', ...payload }); }), - clearAndAddHttpError: action((state, { key, error }) => { - state.items = [ { type: 'error', title: 'Error', key, message: httpErrorToHuman(error) } ]; + clearAndAddHttpError: action((state, payload) => { + if (!payload.error) { + state.items = []; + } else { + console.error(payload.error); + + state.items = [ { + type: 'error', + title: 'Error', + key: payload.key, + message: httpErrorToHuman(payload.error), + } ]; + } }), clearFlashes: action((state, payload) => { diff --git a/routes/api-client.php b/routes/api-client.php index 572f4505b6..5c2bfa9b16 100644 --- a/routes/api-client.php +++ b/routes/api-client.php @@ -29,6 +29,12 @@ Route::get('/api-keys', [Client\ApiKeyController::class, 'index']); Route::post('/api-keys', [Client\ApiKeyController::class, 'store']); Route::delete('/api-keys/{identifier}', [Client\ApiKeyController::class, 'delete']); + + Route::prefix('/ssh-keys')->group(function () { + Route::get('/', [Client\SSHKeyController::class, 'index']); + Route::post('/', [Client\SSHKeyController::class, 'store']); + Route::delete('/{identifier}', [Client\SSHKeyController::class, 'delete']); + }); }); /* From 655416425246639f8c1ca4b7d601b2d90653d5d0 Mon Sep 17 00:00:00 2001 From: DaneEveritt Date: Sat, 14 May 2022 18:08:48 -0400 Subject: [PATCH 030/458] Add test coverage for the SSH key endpoints --- .../Api/Client/SSHKeyController.php | 6 +- .../Api/Client/Account/StoreSSHKeyRequest.php | 4 +- app/Models/UserSSHKey.php | 2 +- ...nsformer.php => UserSSHKeyTransformer.php} | 2 +- database/Factories/UserSSHKeyFactory.php | 52 +++++++ .../Client/ClientApiIntegrationTestCase.php | 4 + .../Api/Client/SSHKeyControllerTest.php | 138 ++++++++++++++++++ 7 files changed, 201 insertions(+), 7 deletions(-) rename app/Transformers/Api/Client/{SSHKeyTransformer.php => UserSSHKeyTransformer.php} (90%) create mode 100644 database/Factories/UserSSHKeyFactory.php create mode 100644 tests/Integration/Api/Client/SSHKeyControllerTest.php diff --git a/app/Http/Controllers/Api/Client/SSHKeyController.php b/app/Http/Controllers/Api/Client/SSHKeyController.php index 5b25a221cc..1b3db52d3f 100644 --- a/app/Http/Controllers/Api/Client/SSHKeyController.php +++ b/app/Http/Controllers/Api/Client/SSHKeyController.php @@ -4,7 +4,7 @@ use Illuminate\Http\JsonResponse; use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest; -use Pterodactyl\Transformers\Api\Client\SSHKeyTransformer; +use Pterodactyl\Transformers\Api\Client\UserSSHKeyTransformer; use Pterodactyl\Http\Requests\Api\Client\Account\StoreSSHKeyRequest; class SSHKeyController extends ClientApiController @@ -16,7 +16,7 @@ class SSHKeyController extends ClientApiController public function index(ClientApiRequest $request): array { return $this->fractal->collection($request->user()->sshKeys) - ->transformWith($this->getTransformer(SSHKeyTransformer::class)) + ->transformWith($this->getTransformer(UserSSHKeyTransformer::class)) ->toArray(); } @@ -32,7 +32,7 @@ public function store(StoreSSHKeyRequest $request): array ]); return $this->fractal->item($model) - ->transformWith($this->getTransformer(SSHKeyTransformer::class)) + ->transformWith($this->getTransformer(UserSSHKeyTransformer::class)) ->toArray(); } diff --git a/app/Http/Requests/Api/Client/Account/StoreSSHKeyRequest.php b/app/Http/Requests/Api/Client/Account/StoreSSHKeyRequest.php index 5705056c48..29bf2d1ba8 100644 --- a/app/Http/Requests/Api/Client/Account/StoreSSHKeyRequest.php +++ b/app/Http/Requests/Api/Client/Account/StoreSSHKeyRequest.php @@ -43,11 +43,11 @@ public function withValidator(Validator $validator): void } if ($this->key instanceof DSA) { - $this->validator->errors()->add('public_key', 'DSA public keys are not supported.'); + $this->validator->errors()->add('public_key', 'DSA keys are not supported.'); } if ($this->key instanceof RSA && $this->key->getLength() < 2048) { - $this->validator->errors()->add('public_key', 'RSA keys must be at 2048 bytes.'); + $this->validator->errors()->add('public_key', 'RSA keys must be at least 2048 bytes in length.'); } $fingerprint = $this->key->getFingerprint('sha256'); diff --git a/app/Models/UserSSHKey.php b/app/Models/UserSSHKey.php index 848ba32c89..718c6d1cfd 100644 --- a/app/Models/UserSSHKey.php +++ b/app/Models/UserSSHKey.php @@ -17,7 +17,6 @@ * @property \Illuminate\Support\Carbon|null $updated_at * @property \Illuminate\Support\Carbon|null $deleted_at * @property \Pterodactyl\Models\User $user - * * @method static \Illuminate\Database\Eloquent\Builder|UserSSHKey newModelQuery() * @method static \Illuminate\Database\Eloquent\Builder|UserSSHKey newQuery() * @method static \Illuminate\Database\Query\Builder|UserSSHKey onlyTrashed() @@ -33,6 +32,7 @@ * @method static \Illuminate\Database\Query\Builder|UserSSHKey withTrashed() * @method static \Illuminate\Database\Query\Builder|UserSSHKey withoutTrashed() * @mixin \Eloquent + * @method static \Database\Factories\UserSSHKeyFactory factory(...$parameters) */ class UserSSHKey extends Model { diff --git a/app/Transformers/Api/Client/SSHKeyTransformer.php b/app/Transformers/Api/Client/UserSSHKeyTransformer.php similarity index 90% rename from app/Transformers/Api/Client/SSHKeyTransformer.php rename to app/Transformers/Api/Client/UserSSHKeyTransformer.php index 1f9b96c290..1a9349e67c 100644 --- a/app/Transformers/Api/Client/SSHKeyTransformer.php +++ b/app/Transformers/Api/Client/UserSSHKeyTransformer.php @@ -4,7 +4,7 @@ use Pterodactyl\Models\UserSSHKey; -class SSHKeyTransformer extends BaseClientTransformer +class UserSSHKeyTransformer extends BaseClientTransformer { public function getResourceName(): string { diff --git a/database/Factories/UserSSHKeyFactory.php b/database/Factories/UserSSHKeyFactory.php new file mode 100644 index 0000000000..5a1a17dcf0 --- /dev/null +++ b/database/Factories/UserSSHKeyFactory.php @@ -0,0 +1,52 @@ + $this->faker->name(), + 'public_key' => 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOaXIq09NH4a93EVdrvHYiZ67Wj+GBEBQ9ou4W0qSYm2', + 'fingerprint' => 'T2b3VnvHWUmfhDOFLqzZg2VfLgqJhWxzrMUy8WZ+V8M', + ]; + } + + /** + * Returns a DSA public key. + */ + public function dsa(): self + { + return $this->state([ + 'public_key' => 'ssh-dss AAAAB3NzaC1kc3MAAACBAPfiWwEFvBOafdUmHDPjXsUttt+65FHSZSCVVeEFOTaL7Y3d0CJyrtck8KS1vmXHSb8QFBY2B1yVSb/reaQvNreWZN3KDYfLbF57/zimBn+IrHrJR+ZglhOxDRHoGPWK7q9jYIrOLwoOjkNKXxz1eOHKUgufFfSNtIRLycEXczLrAAAAFQC6LnBErezotG52jN4JostfC/TfEwAAAIACuTxRzYFDXHAxFICeqqY9w+y+v2yQfdeQ1BgCq2GMagUYfOdqnjizTO9M614r/nXZK1SV10TqhUcQtkJzDQIUtBqzBF5cIC/1cIFKzXi5rNHs8Y4bz/PBD+EbQJdiy+1so1oi790r710bqnkzTravAOJ5rGyfuQRLt+f+kuS9NAAAAIEA7tjGtJuXGUtPIXfnrMYS1iOWryO4irqnvaWfel002/DaGaNjRghNe/cUBYlAsjPhGJ1F7BQlLAY1koliTY6l0svs7ZPBM5QOumrr8OaNXGGVIq/RkkxuZHmRoUL2qH3DGYaktPUn4vFPliiAmGWOHAEu1K6B4g4vG/SKgMRpIvc=', + 'fingerprint' => '9Fb/RODt9N6aldcB+lc6ih0ovr2G/JUjts2Wh21uxYI', + ]); + } + + /** + * Returns an RSA public key, if "weak" is specified a 1024-bit RSA key is returned + * which should fail validation when being stored. + */ + public function rsa(bool $weak = false): self + { + if (!$weak) { + return $this->state([ + 'public_key' => 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCo/YLm2SPSlOIG7AagBSmEe5c0b2PLPzUGFp3gARhD6n6ydBS40TlWzeg2qV95lh6fWBd8LsNgPOFmmuKuNZdBjAGeTY4gxKfHY1vK5/zOI4jPPqAMcCMNfd82aM97kx6dO8Hw1R79OyVpOZylpXLHayVPGHUK37Tpih4W7TeVSMrOqQF9F72lzhwgEtkdjm4gLBL6RpdNXrdnjIaNVnuade0Sb3w384vecZPe+S/997WirOMNy2JU4NdMHEnSjd1/i463RpN96AsXFAu1zl9nrXVhA7DVfSHoigXAqbs/xav8PRpLgAKjYpPohxQ9Nu6tP5jRUhfWdYwNFFp/aWloD/0JdP9LqcBBc9sO9TLkz3fBiUf11VM/QT1UhO84G+ahMxVn95jA472VPUe8uKff69lzbvSavEE6qcQX2TzVKOSi1E26Fzc6IZ/tHEuGEbGFxTsiQ1GysVZ0wr1p6ftd1SVqH5F/oaEK7UO8+xn/syEqaPf6A0eJWRNc0+lHA1sIRjmo9MOBvbkKExkx5JLHgGG81DYDFdZUuHY1BgSxJJcmNWV5BKRm350EbgRngoYI5tB3tCiZVW1PI8qyff9mBae11LY5GPlUeDnPrMvSdCKMIWrg7nC8SbndBCO3Fx4z7G2dTQy4ZmY7Ae9jR4pyg7tTOI3qgl8Z462GZi/jzw==', + 'fingerprint' => 'vjccQdGfqAvuEK3Bki1Ji6aOo3rIuGU0BGJ0ml4CjLQ', + ]); + } + + return $this->state([ + 'public_key' => 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQC4VVsHFO5MxvCtAPyoKGANWyuwZ4fvllvFog5RJbfpDpw8etDFVGEXl+uRR8p79g9oV7MscoFo6HiWrJc4/4vlP665msjosILdIcbnuzMhvXnKisaGh9zflkpyR3KhUxoHxqYp2q8XtffjKKAHz1a8o7OUG6fwaKIqu+d0PoICZQ==', + 'fingerprint' => 'LQSzAAfAsbKpU94gojxXfjGjYcEv8UZIwyzwhcEr/aw', + ]); + } +} diff --git a/tests/Integration/Api/Client/ClientApiIntegrationTestCase.php b/tests/Integration/Api/Client/ClientApiIntegrationTestCase.php index 33f6069a5d..35e8d27191 100644 --- a/tests/Integration/Api/Client/ClientApiIntegrationTestCase.php +++ b/tests/Integration/Api/Client/ClientApiIntegrationTestCase.php @@ -15,6 +15,7 @@ use Pterodactyl\Models\Schedule; use Illuminate\Support\Collection; use Pterodactyl\Models\Allocation; +use Pterodactyl\Models\UserSSHKey; use Pterodactyl\Models\DatabaseHost; use Pterodactyl\Tests\Integration\TestResponse; use Pterodactyl\Tests\Integration\IntegrationTestCase; @@ -77,6 +78,9 @@ protected function link($model, $append = null): string case Backup::class: $link = "/api/client/servers/{$model->server->uuid}/backups/{$model->uuid}"; break; + case UserSSHKey::class: + $link = "/api/client/account/ssh-keys/$model->fingerprint"; + break; default: throw new InvalidArgumentException(sprintf('Cannot create link for Model of type %s', class_basename($model))); } diff --git a/tests/Integration/Api/Client/SSHKeyControllerTest.php b/tests/Integration/Api/Client/SSHKeyControllerTest.php new file mode 100644 index 0000000000..6744a12951 --- /dev/null +++ b/tests/Integration/Api/Client/SSHKeyControllerTest.php @@ -0,0 +1,138 @@ +create(); + $user2 = User::factory()->create(); + + $key = UserSSHKey::factory()->for($user)->create(); + UserSSHKey::factory()->for($user2)->rsa()->create(); + + $this->actingAs($user); + $response = $this->getJson('/api/client/account/ssh-keys') + ->assertOk() + ->assertJsonPath('object', 'list') + ->assertJsonPath('data.0.object', UserSSHKey::RESOURCE_NAME); + + $this->assertJsonTransformedWith($response->json('data.0.attributes'), $key); + } + + /** + * Test that a user's SSH key can be deleted, and that passing the fingerprint + * of another user's SSH key won't delete that key. + */ + public function testSSHKeyCanBeDeleted() + { + $user = User::factory()->create(); + $user2 = User::factory()->create(); + + $key = UserSSHKey::factory()->for($user)->create(); + $key2 = UserSSHKey::factory()->for($user2)->create(); + + $this->actingAs($user); + $this->deleteJson($this->link($key))->assertNoContent(); + + $this->assertSoftDeleted($key); + $this->assertNotSoftDeleted($key2); + + $this->deleteJson($this->link($key))->assertNoContent(); + $this->deleteJson($this->link($key2))->assertNoContent(); + + $this->assertNotSoftDeleted($key2); + } + + public function testDSAKeyIsRejected() + { + $user = User::factory()->create(); + $key = UserSSHKey::factory()->dsa()->make(); + + $this->actingAs($user)->postJson('/api/client/account/ssh-keys', [ + 'name' => 'Name', + 'public_key' => $key->public_key, + ]) + ->assertUnprocessable() + ->assertJsonPath('errors.0.detail', 'DSA keys are not supported.'); + + $this->assertEquals(0, $user->sshKeys()->count()); + } + + public function testWeakRSAKeyIsRejected() + { + $user = User::factory()->create(); + $key = UserSSHKey::factory()->rsa(true)->make(); + + $this->actingAs($user)->postJson('/api/client/account/ssh-keys', [ + 'name' => 'Name', + 'public_key' => $key->public_key, + ]) + ->assertUnprocessable() + ->assertJsonPath('errors.0.detail', 'RSA keys must be at least 2048 bytes in length.'); + + $this->assertEquals(0, $user->sshKeys()->count()); + } + + public function testInvalidOrPrivateKeyIsRejected() + { + $user = User::factory()->create(); + + $this->actingAs($user)->postJson('/api/client/account/ssh-keys', [ + 'name' => 'Name', + 'public_key' => 'invalid', + ]) + ->assertUnprocessable() + ->assertJsonPath('errors.0.detail', 'The public key provided is not valid.'); + + $this->assertEquals(0, $user->sshKeys()->count()); + + $key = EC::createKey('Ed25519'); + $this->actingAs($user)->postJson('/api/client/account/ssh-keys', [ + 'name' => 'Name', + 'public_key' => $key->toString('PKCS8'), + ]) + ->assertUnprocessable() + ->assertJsonPath('errors.0.detail', 'The public key provided is not valid.'); + } + + public function testPublicKeyCanBeStored() + { + $user = User::factory()->create(); + $key = UserSSHKey::factory()->make(); + + $this->actingAs($user)->postJson('/api/client/account/ssh-keys', [ + 'name' => 'Name', + 'public_key' => $key->public_key, + ]) + ->assertOk() + ->assertJsonPath('object', UserSSHKey::RESOURCE_NAME) + ->assertJsonPath('attributes.public_key', $key->public_key); + + $this->assertCount(1, $user->sshKeys); + $this->assertEquals($key->public_key, $user->sshKeys[0]->public_key); + } + + public function testPublicKeyThatAlreadyExistsCannotBeAddedASecondTime() + { + $user = User::factory()->create(); + $key = UserSSHKey::factory()->for($user)->create(); + + $this->actingAs($user)->postJson('/api/client/account/ssh-keys', [ + 'name' => 'Name', + 'public_key' => $key->public_key, + ]) + ->assertUnprocessable() + ->assertJsonPath('errors.0.detail', 'The public key provided already exists on your account.'); + + $this->assertEquals(1, $user->sshKeys()->count()); + } +} From cca0010a00c76ba60aa959a509103bb8e6e5944f Mon Sep 17 00:00:00 2001 From: DaneEveritt Date: Sun, 15 May 2022 14:40:19 -0400 Subject: [PATCH 031/458] Update egg import/update logic to all use the same pathwaus --- app/Observers/EggVariableObserver.php | 22 +++ app/Providers/AppServiceProvider.php | 10 -- app/Providers/EventServiceProvider.php | 21 +++ app/Services/Eggs/EggParserService.php | 89 +++++++++++ .../Eggs/Sharing/EggImporterService.php | 139 ++++-------------- .../Eggs/Sharing/EggUpdateImporterService.php | 106 ++++--------- database/Seeders/EggSeeder.php | 115 +++++---------- .../Application/Nests/EggControllerTest.php | 29 +--- .../Servers/ServerCreationServiceTest.php | 14 +- .../StartupModificationServiceTest.php | 4 +- .../Servers/VariableValidatorServiceTest.php | 25 +++- .../Traits/Integration/CreatesTestModels.php | 29 ++-- 12 files changed, 270 insertions(+), 333 deletions(-) create mode 100644 app/Observers/EggVariableObserver.php create mode 100644 app/Services/Eggs/EggParserService.php diff --git a/app/Observers/EggVariableObserver.php b/app/Observers/EggVariableObserver.php new file mode 100644 index 0000000000..a18718f477 --- /dev/null +++ b/app/Observers/EggVariableObserver.php @@ -0,0 +1,22 @@ +field_type) { + unset($variable->field_type); + } + } + + public function updating(EggVariable $variable): void + { + if ($variable->field_type) { + unset($variable->field_type); + } + } +} diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 4ccc78641e..51fa6e7fd7 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -5,17 +5,11 @@ use View; use Cache; use Illuminate\Support\Str; -use Pterodactyl\Models\User; -use Pterodactyl\Models\Server; -use Pterodactyl\Models\Subuser; use Illuminate\Support\Facades\URL; use Illuminate\Pagination\Paginator; use Illuminate\Support\Facades\Schema; use Illuminate\Support\ServiceProvider; -use Pterodactyl\Observers\UserObserver; use Pterodactyl\Extensions\Themes\Theme; -use Pterodactyl\Observers\ServerObserver; -use Pterodactyl\Observers\SubuserObserver; class AppServiceProvider extends ServiceProvider { @@ -26,10 +20,6 @@ public function boot() { Schema::defaultStringLength(191); - User::observe(UserObserver::class); - Server::observe(ServerObserver::class); - Subuser::observe(SubuserObserver::class); - View::share('appVersion', $this->versionData()['version'] ?? 'undefined'); View::share('appIsGit', $this->versionData()['is_git'] ?? false); diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index 5be9601d60..bf8dfc43d6 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -2,6 +2,14 @@ namespace Pterodactyl\Providers; +use Pterodactyl\Models\User; +use Pterodactyl\Models\Server; +use Pterodactyl\Models\Subuser; +use Pterodactyl\Models\EggVariable; +use Pterodactyl\Observers\UserObserver; +use Pterodactyl\Observers\ServerObserver; +use Pterodactyl\Observers\SubuserObserver; +use Pterodactyl\Observers\EggVariableObserver; use Pterodactyl\Events\Server\Installed as ServerInstalledEvent; use Pterodactyl\Notifications\ServerInstalled as ServerInstalledNotification; use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider; @@ -18,4 +26,17 @@ class EventServiceProvider extends ServiceProvider ServerInstalledNotification::class, ], ]; + + /** + * Boots the service provider and registers model event listeners. + */ + public function boot() + { + parent::boot(); + + User::observe(UserObserver::class); + Server::observe(ServerObserver::class); + Subuser::observe(SubuserObserver::class); + EggVariable::observe(EggVariableObserver::class); + } } diff --git a/app/Services/Eggs/EggParserService.php b/app/Services/Eggs/EggParserService.php new file mode 100644 index 0000000000..ff84fdfd88 --- /dev/null +++ b/app/Services/Eggs/EggParserService.php @@ -0,0 +1,89 @@ +getError() !== UPLOAD_ERR_OK || !$file->isFile()) { + throw new InvalidFileUploadException('The selected file is not valid and cannot be imported.'); + } + + /** @var array $parsed */ + $parsed = json_decode($file->openFile()->fread($file->getSize()), true, 512, JSON_THROW_ON_ERROR); + if (!in_array(Arr::get($parsed, 'meta.version') ?? '', ['PTDL_v1', 'PTDL_v2'])) { + throw new InvalidFileUploadException('The JSON file provided is not in a format that can be recognized.'); + } + + return $this->convertToV2($parsed); + } + + /** + * Fills the provided model with the parsed JSON data. + */ + public function fillFromParsed(Egg $model, array $parsed): Egg + { + return $model->forceFill([ + 'name' => Arr::get($parsed, 'name'), + 'description' => Arr::get($parsed, 'description'), + 'features' => Arr::get($parsed, 'features'), + 'docker_images' => Arr::get($parsed, 'docker_images'), + 'file_denylist' => Collection::make(Arr::get($parsed, 'file_denylist')) + ->filter(fn ($value) => !empty($value)), + 'update_url' => Arr::get($parsed, 'meta.update_url'), + 'config_files' => Arr::get($parsed, 'config.files'), + 'config_startup' => Arr::get($parsed, 'config.startup'), + 'config_logs' => Arr::get($parsed, 'config.logs'), + 'config_stop' => Arr::get($parsed, 'config.stop'), + 'startup' => Arr::get($parsed, 'startup'), + 'script_install' => Arr::get($parsed, 'scripts.installation.script'), + 'script_entry' => Arr::get($parsed, 'scripts.installation.entrypoint'), + 'script_container' => Arr::get($parsed, 'scripts.installation.container'), + ]); + } + + /** + * Converts a PTDL_V1 egg into the expected PTDL_V2 egg format. This just handles + * the "docker_images" field potentially not being present, and not being in the + * expected "key => value" format. + */ + protected function convertToV2(array $parsed): array + { + if (Arr::get($parsed, 'meta.version') === Egg::EXPORT_VERSION) { + return $parsed; + } + + // Maintain backwards compatability for eggs that are still using the old single image + // string format. New eggs can provide an array of Docker images that can be used. + if (!isset($parsed['images'])) { + $images = [Arr::get($parsed, 'image') ?? 'nil']; + } else { + $images = $parsed['images']; + } + + unset($parsed['images'], $parsed['image']); + + $parsed['docker_images'] = []; + foreach ($images as $image) { + $parsed['docker_images'][$image] = $image; + } + + $parsed['variables'] = array_map(function ($value) { + return array_merge($value, ['field_type' => 'text']); + }, $parsed['variables']); + + return $parsed; + } +} diff --git a/app/Services/Eggs/Sharing/EggImporterService.php b/app/Services/Eggs/Sharing/EggImporterService.php index 74f71e1bdb..746ce26be7 100644 --- a/app/Services/Eggs/Sharing/EggImporterService.php +++ b/app/Services/Eggs/Sharing/EggImporterService.php @@ -5,141 +5,52 @@ use Ramsey\Uuid\Uuid; use Illuminate\Support\Arr; use Pterodactyl\Models\Egg; +use Pterodactyl\Models\Nest; use Illuminate\Http\UploadedFile; -use Illuminate\Support\Collection; +use Pterodactyl\Models\EggVariable; use Illuminate\Database\ConnectionInterface; -use Pterodactyl\Contracts\Repository\EggRepositoryInterface; -use Pterodactyl\Contracts\Repository\NestRepositoryInterface; -use Pterodactyl\Exceptions\Service\InvalidFileUploadException; -use Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface; +use Pterodactyl\Services\Eggs\EggParserService; class EggImporterService { - /** - * @var \Illuminate\Database\ConnectionInterface - */ - protected $connection; + protected ConnectionInterface $connection; - /** - * @var \Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface - */ - protected $eggVariableRepository; - - /** - * @var \Pterodactyl\Contracts\Repository\NestRepositoryInterface - */ - protected $nestRepository; + protected EggParserService $parser; - /** - * @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface - */ - protected $repository; - - /** - * EggImporterService constructor. - */ - public function __construct( - ConnectionInterface $connection, - EggRepositoryInterface $repository, - EggVariableRepositoryInterface $eggVariableRepository, - NestRepositoryInterface $nestRepository - ) { + public function __construct(ConnectionInterface $connection, EggParserService $parser) + { $this->connection = $connection; - $this->eggVariableRepository = $eggVariableRepository; - $this->repository = $repository; - $this->nestRepository = $nestRepository; + $this->parser = $parser; } /** * Take an uploaded JSON file and parse it into a new egg. * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - * @throws \Pterodactyl\Exceptions\Service\InvalidFileUploadException - * @throws \JsonException + * @throws \Pterodactyl\Exceptions\Service\InvalidFileUploadException|\Throwable */ public function handle(UploadedFile $file, int $nest): Egg { - if ($file->getError() !== UPLOAD_ERR_OK || !$file->isFile()) { - throw new InvalidFileUploadException(sprintf('The selected file ["%s"] was not in a valid format to import. (is_file: %s is_valid: %s err_code: %s err: %s)', $file->getFilename(), $file->isFile() ? 'true' : 'false', $file->isValid() ? 'true' : 'false', $file->getError(), $file->getErrorMessage())); - } + $parsed = $this->parser->handle($file); - /** @var array $parsed */ - $parsed = json_decode($file->openFile()->fread($file->getSize()), true, 512, JSON_THROW_ON_ERROR); - if (!in_array(Arr::get($parsed, 'meta.version') ?? '', ['PTDL_v1', 'PTDL_v2'])) { - throw new InvalidFileUploadException(trans('exceptions.nest.importer.invalid_json_provided')); - } + /** @var \Pterodactyl\Models\Nest $nest */ + $nest = Nest::query()->with('eggs', 'eggs.variables')->findOrFail($nest); - if ($parsed['meta']['version'] !== Egg::EXPORT_VERSION) { - $parsed = $this->convertV1ToV2($parsed); - } + return $this->connection->transaction(function () use ($nest, $parsed) { + $egg = (new Egg())->forceFill([ + 'uuid' => Uuid::uuid4()->toString(), + 'nest_id' => $nest->id, + 'author' => Arr::get($parsed, 'author'), + 'copy_script_from' => null, + ]); - $nest = $this->nestRepository->getWithEggs($nest); - $this->connection->beginTransaction(); + $egg = $this->parser->fillFromParsed($egg, $parsed); + $egg->save(); - /** @var \Pterodactyl\Models\Egg $egg */ - $egg = $this->repository->create([ - 'uuid' => Uuid::uuid4()->toString(), - 'nest_id' => $nest->id, - 'author' => Arr::get($parsed, 'author'), - 'name' => Arr::get($parsed, 'name'), - 'description' => Arr::get($parsed, 'description'), - 'features' => Arr::get($parsed, 'features'), - 'docker_images' => Arr::get($parsed, 'docker_images'), - 'file_denylist' => Collection::make(Arr::get($parsed, 'file_denylist'))->filter(function ($value) { - return !empty($value); - }), - 'update_url' => Arr::get($parsed, 'meta.update_url'), - 'config_files' => Arr::get($parsed, 'config.files'), - 'config_startup' => Arr::get($parsed, 'config.startup'), - 'config_logs' => Arr::get($parsed, 'config.logs'), - 'config_stop' => Arr::get($parsed, 'config.stop'), - 'startup' => Arr::get($parsed, 'startup'), - 'script_install' => Arr::get($parsed, 'scripts.installation.script'), - 'script_entry' => Arr::get($parsed, 'scripts.installation.entrypoint'), - 'script_container' => Arr::get($parsed, 'scripts.installation.container'), - 'copy_script_from' => null, - ], true, true); + foreach ($parsed['variables'] ?? [] as $variable) { + EggVariable::query()->forceCreate(array_merge($variable, ['egg_id' => $egg->id])); + } - Collection::make($parsed['variables'] ?? [])->each(function (array $variable) use ($egg) { - unset($variable['field_type']); - - $this->eggVariableRepository->create(array_merge($variable, [ - 'egg_id' => $egg->id, - ])); + return $egg; }); - - $this->connection->commit(); - - return $egg; - } - - /** - * Converts a PTDL_V1 egg into the expected PTDL_V2 egg format. This just handles - * the "docker_images" field potentially not being present, and not being in the - * expected "key => value" format. - */ - protected function convertV1ToV2(array $parsed): array - { - // Maintain backwards compatability for eggs that are still using the old single image - // string format. New eggs can provide an array of Docker images that can be used. - if (!isset($parsed['images'])) { - $images = [Arr::get($parsed, 'image') ?? 'nil']; - } else { - $images = $parsed['images']; - } - - unset($parsed['images'], $parsed['image']); - - $parsed['docker_images'] = []; - foreach ($images as $image) { - $parsed['docker_images'][$image] = $image; - } - - $parsed['variables'] = array_map(function ($value) { - return array_merge($value, ['field_type' => 'text']); - }, $parsed['variables']); - - return $parsed; } } diff --git a/app/Services/Eggs/Sharing/EggUpdateImporterService.php b/app/Services/Eggs/Sharing/EggUpdateImporterService.php index 205314314e..7d28116420 100644 --- a/app/Services/Eggs/Sharing/EggUpdateImporterService.php +++ b/app/Services/Eggs/Sharing/EggUpdateImporterService.php @@ -4,105 +4,53 @@ use Pterodactyl\Models\Egg; use Illuminate\Http\UploadedFile; +use Illuminate\Support\Collection; +use Pterodactyl\Models\EggVariable; use Illuminate\Database\ConnectionInterface; -use Pterodactyl\Contracts\Repository\EggRepositoryInterface; -use Pterodactyl\Exceptions\Service\Egg\BadJsonFormatException; -use Pterodactyl\Exceptions\Service\InvalidFileUploadException; -use Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface; +use Pterodactyl\Services\Eggs\EggParserService; class EggUpdateImporterService { - /** - * @var \Illuminate\Database\ConnectionInterface - */ - protected $connection; - - /** - * @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface - */ - protected $repository; + protected ConnectionInterface $connection; - /** - * @var \Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface - */ - protected $variableRepository; + protected EggParserService $parser; /** * EggUpdateImporterService constructor. */ - public function __construct( - ConnectionInterface $connection, - EggRepositoryInterface $repository, - EggVariableRepositoryInterface $variableRepository - ) { + public function __construct(ConnectionInterface $connection, EggParserService $parser) + { $this->connection = $connection; - $this->repository = $repository; - $this->variableRepository = $variableRepository; + $this->parser = $parser; } /** * Update an existing Egg using an uploaded JSON file. * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - * @throws \Pterodactyl\Exceptions\Service\Egg\BadJsonFormatException - * @throws \Pterodactyl\Exceptions\Service\InvalidFileUploadException + * @throws \Pterodactyl\Exceptions\Service\InvalidFileUploadException|\Throwable */ - public function handle(Egg $egg, UploadedFile $file) + public function handle(Egg $egg, UploadedFile $file): Egg { - if ($file->getError() !== UPLOAD_ERR_OK || !$file->isFile()) { - throw new InvalidFileUploadException(sprintf('The selected file ["%s"] was not in a valid format to import. (is_file: %s is_valid: %s err_code: %s err: %s)', $file->getFilename(), $file->isFile() ? 'true' : 'false', $file->isValid() ? 'true' : 'false', $file->getError(), $file->getErrorMessage())); - } - - $parsed = json_decode($file->openFile()->fread($file->getSize())); - if (json_last_error() !== 0) { - throw new BadJsonFormatException(trans('exceptions.nest.importer.json_error', ['error' => json_last_error_msg()])); - } + $parsed = $this->parser->handle($file); + + return $this->connection->transaction(function () use ($egg, $parsed) { + $egg = $this->parser->fillFromParsed($egg, $parsed); + $egg->save(); + + // Update existing variables or create new ones. + foreach ($parsed['variables'] ?? [] as $variable) { + EggVariable::unguarded(function () use ($egg, $variable) { + $egg->variables()->updateOrCreate([ + 'env_variable' => $variable['env_variable'], + ], Collection::make($variable)->except('egg_id', 'env_variable')->toArray()); + }); + } - if (object_get($parsed, 'meta.version') !== 'PTDL_v1') { - throw new InvalidFileUploadException(trans('exceptions.nest.importer.invalid_json_provided')); - } + $imported = array_map(fn ($value) => $value['env_variable'], $parsed['variables'] ?? []); - $this->connection->beginTransaction(); - $this->repository->update($egg->id, [ - 'author' => object_get($parsed, 'author'), - 'name' => object_get($parsed, 'name'), - 'description' => object_get($parsed, 'description'), - 'features' => object_get($parsed, 'features'), - // Maintain backwards compatibility for eggs that are still using the old single image - // string format. New eggs can provide an array of Docker images that can be used. - 'docker_images' => object_get($parsed, 'images') ?? [object_get($parsed, 'image')], - 'config_files' => object_get($parsed, 'config.files'), - 'config_startup' => object_get($parsed, 'config.startup'), - 'config_logs' => object_get($parsed, 'config.logs'), - 'config_stop' => object_get($parsed, 'config.stop'), - 'startup' => object_get($parsed, 'startup'), - 'script_install' => object_get($parsed, 'scripts.installation.script'), - 'script_entry' => object_get($parsed, 'scripts.installation.entrypoint'), - 'script_container' => object_get($parsed, 'scripts.installation.container'), - ], true, true); + $egg->variables()->whereNotIn('env_variable', $imported)->delete(); - // Update Existing Variables - collect($parsed->variables)->each(function ($variable) use ($egg) { - $this->variableRepository->withoutFreshModel()->updateOrCreate([ - 'egg_id' => $egg->id, - 'env_variable' => $variable->env_variable, - ], collect($variable)->except(['egg_id', 'env_variable'])->toArray()); + return $egg->refresh(); }); - - $imported = collect($parsed->variables)->pluck('env_variable')->toArray(); - $existing = $this->variableRepository->setColumns(['id', 'env_variable'])->findWhere([['egg_id', '=', $egg->id]]); - - // Delete variables not present in the import. - collect($existing)->each(function ($variable) use ($egg, $imported) { - if (!in_array($variable->env_variable, $imported)) { - $this->variableRepository->deleteWhere([ - ['egg_id', '=', $egg->id], - ['env_variable', '=', $variable->env_variable], - ]); - } - }); - - $this->connection->commit(); } } diff --git a/database/Seeders/EggSeeder.php b/database/Seeders/EggSeeder.php index 4c7697cf06..234e7b5a43 100644 --- a/database/Seeders/EggSeeder.php +++ b/database/Seeders/EggSeeder.php @@ -2,59 +2,38 @@ namespace Database\Seeders; +use Pterodactyl\Models\Egg; use Pterodactyl\Models\Nest; use Illuminate\Database\Seeder; use Illuminate\Http\UploadedFile; -use Illuminate\Support\Collection; -use Illuminate\Filesystem\Filesystem; use Pterodactyl\Services\Eggs\Sharing\EggImporterService; -use Pterodactyl\Contracts\Repository\EggRepositoryInterface; -use Pterodactyl\Contracts\Repository\NestRepositoryInterface; -use Pterodactyl\Exceptions\Repository\RecordNotFoundException; use Pterodactyl\Services\Eggs\Sharing\EggUpdateImporterService; class EggSeeder extends Seeder { - /** - * @var \Illuminate\Filesystem\Filesystem - */ - private $filesystem; - - /** - * @var \Pterodactyl\Services\Eggs\Sharing\EggImporterService - */ - private $importerService; - - /** - * @var \Pterodactyl\Contracts\Repository\NestRepositoryInterface - */ - private $nestRepository; + protected EggImporterService $importerService; - /** - * @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface - */ - private $repository; + protected EggUpdateImporterService $updateImporterService; /** - * @var \Pterodactyl\Services\Eggs\Sharing\EggUpdateImporterService + * @var string[] */ - private $updateImporterService; + public static array $import = [ + 'Minecraft', + 'Source Engine', + 'Voice Servers', + 'Rust', + ]; /** * EggSeeder constructor. */ public function __construct( EggImporterService $importerService, - EggRepositoryInterface $repository, - EggUpdateImporterService $updateImporterService, - Filesystem $filesystem, - NestRepositoryInterface $nestRepository + EggUpdateImporterService $updateImporterService ) { - $this->filesystem = $filesystem; $this->importerService = $importerService; - $this->repository = $repository; $this->updateImporterService = $updateImporterService; - $this->nestRepository = $nestRepository; } /** @@ -62,72 +41,44 @@ public function __construct( */ public function run() { - $this->getEggsToImport()->each(function ($nest) { - $this->parseEggFiles($this->findMatchingNest($nest)); - }); - } - - /** - * Return a list of eggs to import. - */ - protected function getEggsToImport(): Collection - { - return collect([ - 'Minecraft', - 'Source Engine', - 'Voice Servers', - 'Rust', - ]); - } - - /** - * Find the nest that these eggs should be attached to. - * - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - */ - private function findMatchingNest(string $nestName): Nest - { - return $this->nestRepository->findFirstWhere([ - ['author', '=', 'support@pterodactyl.io'], - ['name', '=', $nestName], - ]); + foreach (static::$import as $nest) { + /* @noinspection PhpParamsInspection */ + $this->parseEggFiles( + Nest::query()->where('author', 'support@pterodactyl.io')->where('name', $nest)->firstOrFail() + ); + } } /** * Loop through the list of egg files and import them. */ - private function parseEggFiles(Nest $nest) + protected function parseEggFiles(Nest $nest) { - $files = $this->filesystem->allFiles(database_path('Seeders/eggs/' . kebab_case($nest->name))); + $files = new \DirectoryIterator(database_path('Seeders/eggs/' . kebab_case($nest->name))); $this->command->alert('Updating Eggs for Nest: ' . $nest->name); - Collection::make($files)->each(function ($file) use ($nest) { - /* @var \Symfony\Component\Finder\SplFileInfo $file */ - $decoded = json_decode($file->getContents()); - if (json_last_error() !== JSON_ERROR_NONE) { - $this->command->error('JSON decode exception for ' . $file->getFilename() . ': ' . json_last_error_msg()); - - return; + /** @var \DirectoryIterator $file */ + foreach ($files as $file) { + if (!$file->isFile() || !$file->isReadable()) { + continue; } + $decoded = json_decode(file_get_contents($file->getRealPath()), true, 512, JSON_THROW_ON_ERROR); $file = new UploadedFile($file->getPathname(), $file->getFilename(), 'application/json'); - try { - $egg = $this->repository->setColumns('id')->findFirstWhere([ - ['author', '=', $decoded->author], - ['name', '=', $decoded->name], - ['nest_id', '=', $nest->id], - ]); + $egg = $nest->eggs() + ->where('author', $decoded['author']) + ->where('name', $decoded['name']) + ->first(); + if ($egg instanceof Egg) { $this->updateImporterService->handle($egg, $file); - - $this->command->info('Updated ' . $decoded->name); - } catch (RecordNotFoundException $exception) { + $this->command->info('Updated ' . $decoded['name']); + } else { $this->importerService->handle($file, $nest->id); - - $this->command->comment('Created ' . $decoded->name); + $this->command->comment('Created ' . $decoded['name']); } - }); + } $this->command->line(''); } diff --git a/tests/Integration/Api/Application/Nests/EggControllerTest.php b/tests/Integration/Api/Application/Nests/EggControllerTest.php index 0482e91eb1..7fc523658f 100644 --- a/tests/Integration/Api/Application/Nests/EggControllerTest.php +++ b/tests/Integration/Api/Application/Nests/EggControllerTest.php @@ -3,34 +3,19 @@ namespace Pterodactyl\Tests\Integration\Api\Application\Nests; use Illuminate\Support\Arr; +use Pterodactyl\Models\Egg; use Illuminate\Http\Response; -use Pterodactyl\Contracts\Repository\EggRepositoryInterface; use Pterodactyl\Transformers\Api\Application\EggTransformer; use Pterodactyl\Tests\Integration\Api\Application\ApplicationApiIntegrationTestCase; class EggControllerTest extends ApplicationApiIntegrationTestCase { - /** - * @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface - */ - private $repository; - - /** - * Setup tests. - */ - public function setUp(): void - { - parent::setUp(); - - $this->repository = $this->app->make(EggRepositoryInterface::class); - } - /** * Test that all the eggs belonging to a given nest can be returned. */ public function testListAllEggsInNest() { - $eggs = $this->repository->findWhere([['nest_id', '=', 1]]); + $eggs = Egg::query()->where('nest_id', 1)->get(); $response = $this->getJson('/api/application/nests/' . $eggs->first()->nest_id . '/eggs'); $response->assertStatus(Response::HTTP_OK); @@ -74,7 +59,7 @@ public function testListAllEggsInNest() */ public function testReturnSingleEgg() { - $egg = $this->repository->find(1); + $egg = Egg::query()->findOrFail(1); $response = $this->getJson('/api/application/nests/' . $egg->nest_id . '/eggs/' . $egg->id); $response->assertStatus(Response::HTTP_OK); @@ -96,7 +81,7 @@ public function testReturnSingleEgg() */ public function testReturnSingleEggWithRelationships() { - $egg = $this->repository->find(1); + $egg = Egg::query()->findOrFail(1); $response = $this->getJson('/api/application/nests/' . $egg->nest_id . '/eggs/' . $egg->id . '?include=servers,variables,nest'); $response->assertStatus(Response::HTTP_OK); @@ -117,7 +102,7 @@ public function testReturnSingleEggWithRelationships() */ public function testGetMissingEgg() { - $egg = $this->repository->find(1); + $egg = Egg::query()->findOrFail(1); $response = $this->getJson('/api/application/nests/' . $egg->nest_id . '/eggs/nil'); $this->assertNotFoundJson($response); @@ -129,7 +114,7 @@ public function testGetMissingEgg() */ public function testErrorReturnedIfNoPermission() { - $egg = $this->repository->find(1); + $egg = Egg::query()->findOrFail(1); $this->createNewDefaultApiKey($this->getApiUser(), ['r_eggs' => 0]); $response = $this->getJson('/api/application/nests/' . $egg->nest_id . '/eggs'); @@ -142,7 +127,7 @@ public function testErrorReturnedIfNoPermission() */ public function testResourceIsNotExposedWithoutPermissions() { - $egg = $this->repository->find(1); + $egg = Egg::query()->findOrFail(1); $this->createNewDefaultApiKey($this->getApiUser(), ['r_eggs' => 0]); $response = $this->getJson('/api/application/nests/' . $egg->nest_id . '/eggs/nil'); diff --git a/tests/Integration/Services/Servers/ServerCreationServiceTest.php b/tests/Integration/Services/Servers/ServerCreationServiceTest.php index 329e3be1d1..c194c21a5e 100644 --- a/tests/Integration/Services/Servers/ServerCreationServiceTest.php +++ b/tests/Integration/Services/Servers/ServerCreationServiceTest.php @@ -25,7 +25,9 @@ class ServerCreationServiceTest extends IntegrationTestCase use WithFaker; /** @var \Mockery\MockInterface */ - private $daemonServerRepository; + protected $daemonServerRepository; + + protected Egg $bungeecord; /** * Stub the calls to Wings so that we don't actually hit those API endpoints. @@ -34,6 +36,12 @@ public function setUp(): void { parent::setUp(); + /* @noinspection PhpFieldAssignmentTypeMismatchInspection */ + $this->bungeecord = Egg::query() + ->where('author', 'support@pterodactyl.io') + ->where('name', 'Bungeecord') + ->firstOrFail(); + $this->daemonServerRepository = Mockery::mock(DaemonServerRepository::class); $this->swap(DaemonServerRepository::class, $this->daemonServerRepository); } @@ -67,7 +75,7 @@ public function testServerIsCreatedWithDeploymentObject() $allocations[0]->port, ]); - $egg = $this->cloneEggAndVariables(Egg::query()->findOrFail(1)); + $egg = $this->cloneEggAndVariables($this->bungeecord); // We want to make sure that the validator service runs as an admin, and not as a regular // user when saving variables. $egg->variables()->first()->update([ @@ -178,7 +186,7 @@ public function testErrorEncounteredByWingsCausesServerToBeDeleted() 'cpu' => 0, 'startup' => 'java server2.jar', 'image' => 'java:8', - 'egg_id' => 1, + 'egg_id' => $this->bungeecord->id, 'environment' => [ 'BUNGEE_VERSION' => '123', 'SERVER_JARFILE' => 'server2.jar', diff --git a/tests/Integration/Services/Servers/StartupModificationServiceTest.php b/tests/Integration/Services/Servers/StartupModificationServiceTest.php index 8f61e1a182..31a1b85a28 100644 --- a/tests/Integration/Services/Servers/StartupModificationServiceTest.php +++ b/tests/Integration/Services/Servers/StartupModificationServiceTest.php @@ -3,7 +3,6 @@ namespace Pterodactyl\Tests\Integration\Services\Servers; use Exception; -use Pterodactyl\Models\Egg; use Pterodactyl\Models\Nest; use Pterodactyl\Models\User; use Pterodactyl\Models\Server; @@ -23,8 +22,7 @@ class StartupModificationServiceTest extends IntegrationTestCase */ public function testNonAdminCanModifyServerVariables() { - // Theoretically lines up with the Bungeecord Minecraft egg. - $server = $this->createServerModel(['egg_id' => 1]); + $server = $this->createServerModel(); try { $this->app->make(StartupModificationService::class)->handle($server, [ diff --git a/tests/Integration/Services/Servers/VariableValidatorServiceTest.php b/tests/Integration/Services/Servers/VariableValidatorServiceTest.php index 01f6f7f020..86a7257468 100644 --- a/tests/Integration/Services/Servers/VariableValidatorServiceTest.php +++ b/tests/Integration/Services/Servers/VariableValidatorServiceTest.php @@ -11,13 +11,25 @@ class VariableValidatorServiceTest extends IntegrationTestCase { + protected Egg $egg; + + public function setUp(): void + { + parent::setUp(); + + /* @noinspection PhpFieldAssignmentTypeMismatchInspection */ + $this->egg = Egg::query() + ->where('author', 'support@pterodactyl.io') + ->where('name', 'Bungeecord') + ->firstOrFail(); + } + /** * Test that enviornment variables for a server are validated as expected. */ public function testEnvironmentVariablesCanBeValidated() { - /** @noinspection PhpParamsInspection */ - $egg = $this->cloneEggAndVariables(Egg::query()->findOrFail(1)); + $egg = $this->cloneEggAndVariables($this->egg); try { $this->getService()->handle($egg->id, [ @@ -54,8 +66,7 @@ public function testEnvironmentVariablesCanBeValidated() */ public function testNormalUserCannotValidateNonUserEditableVariables() { - /** @noinspection PhpParamsInspection */ - $egg = $this->cloneEggAndVariables(Egg::query()->findOrFail(1)); + $egg = $this->cloneEggAndVariables($this->egg); $egg->variables()->first()->update([ 'user_editable' => false, ]); @@ -74,8 +85,7 @@ public function testNormalUserCannotValidateNonUserEditableVariables() public function testEnvironmentVariablesCanBeUpdatedAsAdmin() { - /** @noinspection PhpParamsInspection */ - $egg = $this->cloneEggAndVariables(Egg::query()->findOrFail(1)); + $egg = $this->cloneEggAndVariables($this->egg); $egg->variables()->first()->update([ 'user_editable' => false, ]); @@ -107,8 +117,7 @@ public function testEnvironmentVariablesCanBeUpdatedAsAdmin() public function testNullableEnvironmentVariablesCanBeUsedCorrectly() { - /** @noinspection PhpParamsInspection */ - $egg = $this->cloneEggAndVariables(Egg::query()->findOrFail(1)); + $egg = $this->cloneEggAndVariables($this->egg); $egg->variables()->where('env_variable', '!=', 'BUNGEE_VERSION')->delete(); $egg->variables()->update(['rules' => 'nullable|string']); diff --git a/tests/Traits/Integration/CreatesTestModels.php b/tests/Traits/Integration/CreatesTestModels.php index bafa9baa88..0885ccc5af 100644 --- a/tests/Traits/Integration/CreatesTestModels.php +++ b/tests/Traits/Integration/CreatesTestModels.php @@ -4,7 +4,6 @@ use Ramsey\Uuid\Uuid; use Pterodactyl\Models\Egg; -use Pterodactyl\Models\Nest; use Pterodactyl\Models\Node; use Pterodactyl\Models\User; use Pterodactyl\Models\Server; @@ -52,20 +51,17 @@ public function createServerModel(array $attributes = []) $attributes['allocation_id'] = $allocation->id; } - if (!isset($attributes['nest_id'])) { - /** @var \Pterodactyl\Models\Nest $nest */ - $nest = Nest::with('eggs')->first(); - $attributes['nest_id'] = $nest->id; + if (empty($attributes['egg_id'])) { + $egg = !empty($attributes['nest_id']) + ? Egg::query()->where('nest_id', $attributes['nest_id'])->firstOrFail() + : $this->getBungeecordEgg(); - if (!isset($attributes['egg_id'])) { - $attributes['egg_id'] = $nest->getRelation('eggs')->first()->id; - } + $attributes['egg_id'] = $egg->id; + $attributes['nest_id'] = $egg->nest_id; } - if (!isset($attributes['egg_id'])) { - /** @var \Pterodactyl\Models\Egg $egg */ - $egg = Egg::where('nest_id', $attributes['nest_id'])->first(); - $attributes['egg_id'] = $egg->id; + if (empty($attributes['nest_id'])) { + $attributes['nest_id'] = Egg::query()->findOrFail($attributes['egg_id'])->nest_id; } unset($attributes['user_id'], $attributes['location_id']); @@ -99,4 +95,13 @@ protected function cloneEggAndVariables(Egg $egg): Egg return $model->fresh(); } + + /** + * Most every test just assumes it is using Bungeecord — this is the critical + * egg model for all tests unless specified otherwise. + */ + private function getBungeecordEgg() + { + return Egg::query()->where('author', 'support@pterodactyl.io')->where('name', 'Bungeecord')->firstOrFail(); + } } From 12927a32022fceb1343afdc92cac9ca483b12817 Mon Sep 17 00:00:00 2001 From: DaneEveritt Date: Sun, 15 May 2022 15:37:58 -0400 Subject: [PATCH 032/458] Update SFTP authentication endpoint to support returning user public keys --- .../Remote/SftpAuthenticationController.php | 124 +++++++++++------- .../Remote/SftpAuthenticationFormRequest.php | 8 +- 2 files changed, 84 insertions(+), 48 deletions(-) diff --git a/app/Http/Controllers/Api/Remote/SftpAuthenticationController.php b/app/Http/Controllers/Api/Remote/SftpAuthenticationController.php index 4a04c30890..b139ab3cf6 100644 --- a/app/Http/Controllers/Api/Remote/SftpAuthenticationController.php +++ b/app/Http/Controllers/Api/Remote/SftpAuthenticationController.php @@ -2,94 +2,133 @@ namespace Pterodactyl\Http\Controllers\Api\Remote; +use Pterodactyl\Models\User; use Illuminate\Http\Request; +use Pterodactyl\Models\Server; use Illuminate\Http\JsonResponse; use Pterodactyl\Models\Permission; use Pterodactyl\Http\Controllers\Controller; use Illuminate\Foundation\Auth\ThrottlesLogins; -use Pterodactyl\Repositories\Eloquent\UserRepository; use Pterodactyl\Exceptions\Http\HttpForbiddenException; -use Pterodactyl\Repositories\Eloquent\ServerRepository; use Pterodactyl\Services\Servers\GetUserPermissionsService; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Pterodactyl\Http\Requests\Api\Remote\SftpAuthenticationFormRequest; use Symfony\Component\HttpKernel\Exception\TooManyRequestsHttpException; -class SftpAuthenticationController extends Controller +abstract class SftpAuthenticationController extends Controller { use ThrottlesLogins; - /** - * @var \Pterodactyl\Repositories\Eloquent\UserRepository - */ - private $userRepository; + protected GetUserPermissionsService $permissions; + + public function __construct(GetUserPermissionsService $permissions) + { + $this->permissions = $permissions; + } /** - * @var \Pterodactyl\Repositories\Eloquent\ServerRepository + * Authenticate a set of credentials and return the associated server details + * for a SFTP connection on the daemon. */ - private $serverRepository; + public function __invoke(SftpAuthenticationFormRequest $request): JsonResponse + { + $connection = $this->parseUsername($request->input('username')); + + $this->validateRequestState($request); + + $user = $this->getUser($request, $connection['username']); + $server = $this->getServer($request, $connection['server']); + + if ($request->input('type') !== 'public_key') { + if (!password_verify($request->input('password'), $user->password)) { + $this->reject($request); + } + } + + $this->validateSftpAccess($user, $server); + + return new JsonResponse([ + 'server' => $server->uuid, + 'public_keys' => $user->sshKeys->map(fn ($value) => $value->public_key)->toArray(), + 'permissions' => $permissions ?? ['*'], + ]); + } /** - * @var \Pterodactyl\Services\Servers\GetUserPermissionsService + * Finds the server being requested and ensures that it belongs to the node this + * request stems from. */ - private $permissionsService; + protected function getServer(Request $request, string $uuid): Server + { + return Server::query() + ->where(fn ($builder) => $builder->where('uuid', $uuid)->orWhere('uuidShort', $uuid)) + ->where('node_id', $request->attributes->get('node')->id) + ->firstOr(function () use ($request) { + $this->reject($request); + }); + } /** - * SftpController constructor. + * Finds a user with the given username or increments the login attempts. */ - public function __construct( - GetUserPermissionsService $permissionsService, - UserRepository $userRepository, - ServerRepository $serverRepository - ) { - $this->userRepository = $userRepository; - $this->serverRepository = $serverRepository; - $this->permissionsService = $permissionsService; + protected function getUser(Request $request, string $username): User + { + return User::query()->where('username', $username)->firstOr(function () use ($request) { + $this->reject($request); + }); } /** - * Authenticate a set of credentials and return the associated server details - * for a SFTP connection on the daemon. + * Parses the username provided to the request. * - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @return array{"username": string, "server": string} */ - public function __invoke(SftpAuthenticationFormRequest $request): JsonResponse + protected function parseUsername(string $value): array { // Reverse the string to avoid issues with usernames that contain periods. - $parts = explode('.', strrev($request->input('username')), 2); + $parts = explode('.', strrev($value), 2); // Unreverse the strings after parsing them apart. - $connection = [ + return [ 'username' => strrev(array_get($parts, 1)), 'server' => strrev(array_get($parts, 0)), ]; + } + /** + * Checks that the request should not be throttled yet, and that the server was + * provided in the username. + */ + protected function validateRequestState(Request $request): void + { if ($this->hasTooManyLoginAttempts($request)) { $seconds = $this->limiter()->availableIn($this->throttleKey($request)); throw new TooManyRequestsHttpException($seconds, "Too many login attempts for this account, please try again in {$seconds} seconds."); } - /** @var \Pterodactyl\Models\Node $node */ - $node = $request->attributes->get('node'); if (empty($connection['server'])) { throw new NotFoundHttpException(); } + } - /** @var \Pterodactyl\Models\User $user */ - $user = $this->userRepository->findFirstWhere([ - ['username', '=', $connection['username']], - ]); - - $server = $this->serverRepository->getByUuid($connection['server'] ?? ''); - if (!password_verify($request->input('password'), $user->password) || $server->node_id !== $node->id) { - $this->incrementLoginAttempts($request); + /** + * Rejects the request and increments the login attempts. + */ + protected function reject(Request $request): void + { + $this->incrementLoginAttempts($request); - throw new HttpForbiddenException('Authorization credentials were not correct, please try again.'); - } + throw new HttpForbiddenException('Authorization credentials were not correct, please try again.'); + } + /** + * Validates that a user should have permission to use SFTP for the given server. + */ + protected function validateSftpAccess(User $user, Server $server): void + { if (!$user->root_admin && $server->owner_id !== $user->id) { - $permissions = $this->permissionsService->handle($server, $user); + $permissions = $this->permissions->handle($server, $user); if (!in_array(Permission::ACTION_FILE_SFTP, $permissions)) { throw new HttpForbiddenException('You do not have permission to access SFTP for this server.'); @@ -97,13 +136,6 @@ public function __invoke(SftpAuthenticationFormRequest $request): JsonResponse } $server->validateCurrentState(); - - return new JsonResponse([ - 'server' => $server->uuid, - // Deprecated, but still needed at the moment for Wings. - 'token' => '', - 'permissions' => $permissions ?? ['*'], - ]); } /** diff --git a/app/Http/Requests/Api/Remote/SftpAuthenticationFormRequest.php b/app/Http/Requests/Api/Remote/SftpAuthenticationFormRequest.php index 041ff197fe..bb6ec5be8e 100644 --- a/app/Http/Requests/Api/Remote/SftpAuthenticationFormRequest.php +++ b/app/Http/Requests/Api/Remote/SftpAuthenticationFormRequest.php @@ -2,6 +2,7 @@ namespace Pterodactyl\Http\Requests\Api\Remote; +use Illuminate\Validation\Rule; use Illuminate\Foundation\Http\FormRequest; class SftpAuthenticationFormRequest extends FormRequest @@ -24,8 +25,11 @@ public function authorize() public function rules() { return [ - 'username' => 'required|string', - 'password' => 'required|string', + 'type' => ['nullable', 'in:password,public_key'], + 'username' => ['required', 'string'], + 'password' => [ + Rule::when(fn () => $this->input('type') !== 'public_key', ['required', 'string'], ['nullable']), + ], ]; } From e856daee195c5031a8944e739209966cdce83d5b Mon Sep 17 00:00:00 2001 From: DaneEveritt Date: Sun, 15 May 2022 15:47:06 -0400 Subject: [PATCH 033/458] Reject requests for public key auth when the user has no keys --- .../Controllers/Api/Remote/SftpAuthenticationController.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/Http/Controllers/Api/Remote/SftpAuthenticationController.php b/app/Http/Controllers/Api/Remote/SftpAuthenticationController.php index b139ab3cf6..51b02ddf65 100644 --- a/app/Http/Controllers/Api/Remote/SftpAuthenticationController.php +++ b/app/Http/Controllers/Api/Remote/SftpAuthenticationController.php @@ -43,6 +43,12 @@ public function __invoke(SftpAuthenticationFormRequest $request): JsonResponse if (!password_verify($request->input('password'), $user->password)) { $this->reject($request); } + } else { + // Start blocking requests when the user has no public keys in the first place — + // don't let the user spam this endpoint. + if ($user->sshKeys->isEmpty()) { + $this->reject($request); + } } $this->validateSftpAccess($user, $server); From 412ac5ef39589dd48adf61600dca6dc4320f00cc Mon Sep 17 00:00:00 2001 From: DaneEveritt Date: Sun, 15 May 2022 16:00:08 -0400 Subject: [PATCH 034/458] Have the panel handle all of the authorization for both public key and password based attempts --- .../Api/Remote/SftpAuthenticationController.php | 7 +++---- .../Requests/Api/Remote/SftpAuthenticationFormRequest.php | 5 +---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/app/Http/Controllers/Api/Remote/SftpAuthenticationController.php b/app/Http/Controllers/Api/Remote/SftpAuthenticationController.php index 51b02ddf65..3259cd8f3c 100644 --- a/app/Http/Controllers/Api/Remote/SftpAuthenticationController.php +++ b/app/Http/Controllers/Api/Remote/SftpAuthenticationController.php @@ -28,7 +28,8 @@ public function __construct(GetUserPermissionsService $permissions) /** * Authenticate a set of credentials and return the associated server details - * for a SFTP connection on the daemon. + * for a SFTP connection on the daemon. This supports both public key and password + * based credentials. */ public function __invoke(SftpAuthenticationFormRequest $request): JsonResponse { @@ -44,9 +45,7 @@ public function __invoke(SftpAuthenticationFormRequest $request): JsonResponse $this->reject($request); } } else { - // Start blocking requests when the user has no public keys in the first place — - // don't let the user spam this endpoint. - if ($user->sshKeys->isEmpty()) { + if (!$user->sshKeys()->where('public_key', $request->input('password'))->exists()) { $this->reject($request); } } diff --git a/app/Http/Requests/Api/Remote/SftpAuthenticationFormRequest.php b/app/Http/Requests/Api/Remote/SftpAuthenticationFormRequest.php index bb6ec5be8e..f1e06cb3f5 100644 --- a/app/Http/Requests/Api/Remote/SftpAuthenticationFormRequest.php +++ b/app/Http/Requests/Api/Remote/SftpAuthenticationFormRequest.php @@ -2,7 +2,6 @@ namespace Pterodactyl\Http\Requests\Api\Remote; -use Illuminate\Validation\Rule; use Illuminate\Foundation\Http\FormRequest; class SftpAuthenticationFormRequest extends FormRequest @@ -27,9 +26,7 @@ public function rules() return [ 'type' => ['nullable', 'in:password,public_key'], 'username' => ['required', 'string'], - 'password' => [ - Rule::when(fn () => $this->input('type') !== 'public_key', ['required', 'string'], ['nullable']), - ], + 'password' => ['required', 'string'], ]; } From 3d6a30c9fd773febf0129172e63f7e3af19abe74 Mon Sep 17 00:00:00 2001 From: DaneEveritt Date: Sun, 15 May 2022 16:06:00 -0400 Subject: [PATCH 035/458] Oops, don't make this abstract --- .../Controllers/Api/Remote/SftpAuthenticationController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Http/Controllers/Api/Remote/SftpAuthenticationController.php b/app/Http/Controllers/Api/Remote/SftpAuthenticationController.php index 3259cd8f3c..a9a41f4c09 100644 --- a/app/Http/Controllers/Api/Remote/SftpAuthenticationController.php +++ b/app/Http/Controllers/Api/Remote/SftpAuthenticationController.php @@ -15,7 +15,7 @@ use Pterodactyl\Http\Requests\Api\Remote\SftpAuthenticationFormRequest; use Symfony\Component\HttpKernel\Exception\TooManyRequestsHttpException; -abstract class SftpAuthenticationController extends Controller +class SftpAuthenticationController extends Controller { use ThrottlesLogins; From b563f13d097dadfcfa72af41f3041321d371e73e Mon Sep 17 00:00:00 2001 From: DaneEveritt Date: Sun, 15 May 2022 16:23:17 -0400 Subject: [PATCH 036/458] Trim the key provided to query correctly; don't increment throttles when keys aren't found --- .../Remote/SftpAuthenticationController.php | 38 ++++++++----------- 1 file changed, 15 insertions(+), 23 deletions(-) diff --git a/app/Http/Controllers/Api/Remote/SftpAuthenticationController.php b/app/Http/Controllers/Api/Remote/SftpAuthenticationController.php index a9a41f4c09..a45540938e 100644 --- a/app/Http/Controllers/Api/Remote/SftpAuthenticationController.php +++ b/app/Http/Controllers/Api/Remote/SftpAuthenticationController.php @@ -11,7 +11,7 @@ use Illuminate\Foundation\Auth\ThrottlesLogins; use Pterodactyl\Exceptions\Http\HttpForbiddenException; use Pterodactyl\Services\Servers\GetUserPermissionsService; -use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; use Pterodactyl\Http\Requests\Api\Remote\SftpAuthenticationFormRequest; use Symfony\Component\HttpKernel\Exception\TooManyRequestsHttpException; @@ -34,8 +34,15 @@ public function __construct(GetUserPermissionsService $permissions) public function __invoke(SftpAuthenticationFormRequest $request): JsonResponse { $connection = $this->parseUsername($request->input('username')); + if (empty($connection['server'])) { + throw new BadRequestHttpException('No valid server identifier was included in the request.'); + } - $this->validateRequestState($request); + if ($this->hasTooManyLoginAttempts($request)) { + $seconds = $this->limiter()->availableIn($this->throttleKey($request)); + + throw new TooManyRequestsHttpException($seconds, "Too many login attempts for this account, please try again in {$seconds} seconds."); + } $user = $this->getUser($request, $connection['username']); $server = $this->getServer($request, $connection['server']); @@ -45,8 +52,8 @@ public function __invoke(SftpAuthenticationFormRequest $request): JsonResponse $this->reject($request); } } else { - if (!$user->sshKeys()->where('public_key', $request->input('password'))->exists()) { - $this->reject($request); + if (!$user->sshKeys()->where('public_key', trim($request->input('password')))->exists()) { + $this->reject($request, false); } } @@ -100,29 +107,14 @@ protected function parseUsername(string $value): array ]; } - /** - * Checks that the request should not be throttled yet, and that the server was - * provided in the username. - */ - protected function validateRequestState(Request $request): void - { - if ($this->hasTooManyLoginAttempts($request)) { - $seconds = $this->limiter()->availableIn($this->throttleKey($request)); - - throw new TooManyRequestsHttpException($seconds, "Too many login attempts for this account, please try again in {$seconds} seconds."); - } - - if (empty($connection['server'])) { - throw new NotFoundHttpException(); - } - } - /** * Rejects the request and increments the login attempts. */ - protected function reject(Request $request): void + protected function reject(Request $request, bool $increment = true): void { - $this->incrementLoginAttempts($request); + if ($increment) { + $this->incrementLoginAttempts($request); + } throw new HttpForbiddenException('Authorization credentials were not correct, please try again.'); } From a9364061c1605ffa27af8b8a1ececbad25a9a9ee Mon Sep 17 00:00:00 2001 From: DaneEveritt Date: Sun, 15 May 2022 16:41:15 -0400 Subject: [PATCH 037/458] Store keys in standard format; query with fingerprint not public key --- app/Http/Controllers/Api/Client/SSHKeyController.php | 2 +- .../Api/Remote/SftpAuthenticationController.php | 12 ++++++++++-- .../Api/Client/Account/StoreSSHKeyRequest.php | 8 ++++++++ 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/app/Http/Controllers/Api/Client/SSHKeyController.php b/app/Http/Controllers/Api/Client/SSHKeyController.php index 1b3db52d3f..80ea6dda7e 100644 --- a/app/Http/Controllers/Api/Client/SSHKeyController.php +++ b/app/Http/Controllers/Api/Client/SSHKeyController.php @@ -27,7 +27,7 @@ public function store(StoreSSHKeyRequest $request): array { $model = $request->user()->sshKeys()->create([ 'name' => $request->input('name'), - 'public_key' => $request->input('public_key'), + 'public_key' => $request->getPublicKey(), 'fingerprint' => $request->getKeyFingerprint(), ]); diff --git a/app/Http/Controllers/Api/Remote/SftpAuthenticationController.php b/app/Http/Controllers/Api/Remote/SftpAuthenticationController.php index a45540938e..7d42615078 100644 --- a/app/Http/Controllers/Api/Remote/SftpAuthenticationController.php +++ b/app/Http/Controllers/Api/Remote/SftpAuthenticationController.php @@ -7,7 +7,9 @@ use Pterodactyl\Models\Server; use Illuminate\Http\JsonResponse; use Pterodactyl\Models\Permission; +use phpseclib3\Crypt\PublicKeyLoader; use Pterodactyl\Http\Controllers\Controller; +use phpseclib3\Exception\NoKeyLoadedException; use Illuminate\Foundation\Auth\ThrottlesLogins; use Pterodactyl\Exceptions\Http\HttpForbiddenException; use Pterodactyl\Services\Servers\GetUserPermissionsService; @@ -52,7 +54,14 @@ public function __invoke(SftpAuthenticationFormRequest $request): JsonResponse $this->reject($request); } } else { - if (!$user->sshKeys()->where('public_key', trim($request->input('password')))->exists()) { + $key = null; + try { + $key = PublicKeyLoader::loadPublicKey(trim($request->input('password'))); + } catch (NoKeyLoadedException $exception) { + // do nothing + } + + if (!$key || !$user->sshKeys()->where('fingerprint', $key->getFingerprint('sha256'))->exists()) { $this->reject($request, false); } } @@ -61,7 +70,6 @@ public function __invoke(SftpAuthenticationFormRequest $request): JsonResponse return new JsonResponse([ 'server' => $server->uuid, - 'public_keys' => $user->sshKeys->map(fn ($value) => $value->public_key)->toArray(), 'permissions' => $permissions ?? ['*'], ]); } diff --git a/app/Http/Requests/Api/Client/Account/StoreSSHKeyRequest.php b/app/Http/Requests/Api/Client/Account/StoreSSHKeyRequest.php index 29bf2d1ba8..bddea153b6 100644 --- a/app/Http/Requests/Api/Client/Account/StoreSSHKeyRequest.php +++ b/app/Http/Requests/Api/Client/Account/StoreSSHKeyRequest.php @@ -57,6 +57,14 @@ public function withValidator(Validator $validator): void }); } + /** + * Returns the public key but formatted in a consistent manner. + */ + public function getPublicKey(): string + { + return $this->key->toString('PKCS8'); + } + /** * Returns the SHA256 fingerprint of the key provided. */ From 8483db755dd0a06d1d12305677216b2781c56cd8 Mon Sep 17 00:00:00 2001 From: DaneEveritt Date: Sun, 15 May 2022 16:47:58 -0400 Subject: [PATCH 038/458] Fix SSH key factory --- database/Factories/UserSSHKeyFactory.php | 36 ++++++++++++++++-------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/database/Factories/UserSSHKeyFactory.php b/database/Factories/UserSSHKeyFactory.php index 5a1a17dcf0..ab20b251d2 100644 --- a/database/Factories/UserSSHKeyFactory.php +++ b/database/Factories/UserSSHKeyFactory.php @@ -2,10 +2,23 @@ namespace Database\Factories; +use phpseclib3\Crypt\PublicKeyLoader; use Illuminate\Database\Eloquent\Factories\Factory; class UserSSHKeyFactory extends Factory { + /** + * Some fake public keys generated for test purposes. + * + * @var string[] + */ + protected static array $keys = [ + 'dsa' => 'ssh-dss AAAAB3NzaC1kc3MAAACBAPfiWwEFvBOafdUmHDPjXsUttt+65FHSZSCVVeEFOTaL7Y3d0CJyrtck8KS1vmXHSb8QFBY2B1yVSb/reaQvNreWZN3KDYfLbF57/zimBn+IrHrJR+ZglhOxDRHoGPWK7q9jYIrOLwoOjkNKXxz1eOHKUgufFfSNtIRLycEXczLrAAAAFQC6LnBErezotG52jN4JostfC/TfEwAAAIACuTxRzYFDXHAxFICeqqY9w+y+v2yQfdeQ1BgCq2GMagUYfOdqnjizTO9M614r/nXZK1SV10TqhUcQtkJzDQIUtBqzBF5cIC/1cIFKzXi5rNHs8Y4bz/PBD+EbQJdiy+1so1oi790r710bqnkzTravAOJ5rGyfuQRLt+f+kuS9NAAAAIEA7tjGtJuXGUtPIXfnrMYS1iOWryO4irqnvaWfel002/DaGaNjRghNe/cUBYlAsjPhGJ1F7BQlLAY1koliTY6l0svs7ZPBM5QOumrr8OaNXGGVIq/RkkxuZHmRoUL2qH3DGYaktPUn4vFPliiAmGWOHAEu1K6B4g4vG/SKgMRpIvc=', + 'rsa_2048' => 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQC4VVsHFO5MxvCtAPyoKGANWyuwZ4fvllvFog5RJbfpDpw8etDFVGEXl+uRR8p79g9oV7MscoFo6HiWrJc4/4vlP665msjosILdIcbnuzMhvXnKisaGh9zflkpyR3KhUxoHxqYp2q8XtffjKKAHz1a8o7OUG6fwaKIqu+d0PoICZQ==', + 'rsa_4096' => 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCo/YLm2SPSlOIG7AagBSmEe5c0b2PLPzUGFp3gARhD6n6ydBS40TlWzeg2qV95lh6fWBd8LsNgPOFmmuKuNZdBjAGeTY4gxKfHY1vK5/zOI4jPPqAMcCMNfd82aM97kx6dO8Hw1R79OyVpOZylpXLHayVPGHUK37Tpih4W7TeVSMrOqQF9F72lzhwgEtkdjm4gLBL6RpdNXrdnjIaNVnuade0Sb3w384vecZPe+S/997WirOMNy2JU4NdMHEnSjd1/i463RpN96AsXFAu1zl9nrXVhA7DVfSHoigXAqbs/xav8PRpLgAKjYpPohxQ9Nu6tP5jRUhfWdYwNFFp/aWloD/0JdP9LqcBBc9sO9TLkz3fBiUf11VM/QT1UhO84G+ahMxVn95jA472VPUe8uKff69lzbvSavEE6qcQX2TzVKOSi1E26Fzc6IZ/tHEuGEbGFxTsiQ1GysVZ0wr1p6ftd1SVqH5F/oaEK7UO8+xn/syEqaPf6A0eJWRNc0+lHA1sIRjmo9MOBvbkKExkx5JLHgGG81DYDFdZUuHY1BgSxJJcmNWV5BKRm350EbgRngoYI5tB3tCiZVW1PI8qyff9mBae11LY5GPlUeDnPrMvSdCKMIWrg7nC8SbndBCO3Fx4z7G2dTQy4ZmY7Ae9jR4pyg7tTOI3qgl8Z462GZi/jzw==', + 'ed25519' => 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOaXIq09NH4a93EVdrvHYiZ67Wj+GBEBQ9ou4W0qSYm2', + ]; + /** * Returns a fake public key for use on the system. * @@ -13,10 +26,12 @@ class UserSSHKeyFactory extends Factory */ public function definition() { + $key = PublicKeyLoader::loadPublicKey(static::$keys['ed25519']); + return [ 'name' => $this->faker->name(), - 'public_key' => 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOaXIq09NH4a93EVdrvHYiZ67Wj+GBEBQ9ou4W0qSYm2', - 'fingerprint' => 'T2b3VnvHWUmfhDOFLqzZg2VfLgqJhWxzrMUy8WZ+V8M', + 'public_key' => $key->toString('PKCS8'), + 'fingerprint' => $key->getFingerprint('sha256'), ]; } @@ -25,9 +40,11 @@ public function definition() */ public function dsa(): self { + $key = PublicKeyLoader::loadPublicKey(static::$keys['dsa']); + return $this->state([ - 'public_key' => 'ssh-dss AAAAB3NzaC1kc3MAAACBAPfiWwEFvBOafdUmHDPjXsUttt+65FHSZSCVVeEFOTaL7Y3d0CJyrtck8KS1vmXHSb8QFBY2B1yVSb/reaQvNreWZN3KDYfLbF57/zimBn+IrHrJR+ZglhOxDRHoGPWK7q9jYIrOLwoOjkNKXxz1eOHKUgufFfSNtIRLycEXczLrAAAAFQC6LnBErezotG52jN4JostfC/TfEwAAAIACuTxRzYFDXHAxFICeqqY9w+y+v2yQfdeQ1BgCq2GMagUYfOdqnjizTO9M614r/nXZK1SV10TqhUcQtkJzDQIUtBqzBF5cIC/1cIFKzXi5rNHs8Y4bz/PBD+EbQJdiy+1so1oi790r710bqnkzTravAOJ5rGyfuQRLt+f+kuS9NAAAAIEA7tjGtJuXGUtPIXfnrMYS1iOWryO4irqnvaWfel002/DaGaNjRghNe/cUBYlAsjPhGJ1F7BQlLAY1koliTY6l0svs7ZPBM5QOumrr8OaNXGGVIq/RkkxuZHmRoUL2qH3DGYaktPUn4vFPliiAmGWOHAEu1K6B4g4vG/SKgMRpIvc=', - 'fingerprint' => '9Fb/RODt9N6aldcB+lc6ih0ovr2G/JUjts2Wh21uxYI', + 'public_key' => $key->toString('PKCS8'), + 'fingerprint' => $key->getFingerprint('sha256'), ]); } @@ -37,16 +54,11 @@ public function dsa(): self */ public function rsa(bool $weak = false): self { - if (!$weak) { - return $this->state([ - 'public_key' => 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCo/YLm2SPSlOIG7AagBSmEe5c0b2PLPzUGFp3gARhD6n6ydBS40TlWzeg2qV95lh6fWBd8LsNgPOFmmuKuNZdBjAGeTY4gxKfHY1vK5/zOI4jPPqAMcCMNfd82aM97kx6dO8Hw1R79OyVpOZylpXLHayVPGHUK37Tpih4W7TeVSMrOqQF9F72lzhwgEtkdjm4gLBL6RpdNXrdnjIaNVnuade0Sb3w384vecZPe+S/997WirOMNy2JU4NdMHEnSjd1/i463RpN96AsXFAu1zl9nrXVhA7DVfSHoigXAqbs/xav8PRpLgAKjYpPohxQ9Nu6tP5jRUhfWdYwNFFp/aWloD/0JdP9LqcBBc9sO9TLkz3fBiUf11VM/QT1UhO84G+ahMxVn95jA472VPUe8uKff69lzbvSavEE6qcQX2TzVKOSi1E26Fzc6IZ/tHEuGEbGFxTsiQ1GysVZ0wr1p6ftd1SVqH5F/oaEK7UO8+xn/syEqaPf6A0eJWRNc0+lHA1sIRjmo9MOBvbkKExkx5JLHgGG81DYDFdZUuHY1BgSxJJcmNWV5BKRm350EbgRngoYI5tB3tCiZVW1PI8qyff9mBae11LY5GPlUeDnPrMvSdCKMIWrg7nC8SbndBCO3Fx4z7G2dTQy4ZmY7Ae9jR4pyg7tTOI3qgl8Z462GZi/jzw==', - 'fingerprint' => 'vjccQdGfqAvuEK3Bki1Ji6aOo3rIuGU0BGJ0ml4CjLQ', - ]); - } + $key = PublicKeyLoader::loadPublicKey(static::$keys[$weak ? 'rsa_2048' : 'rsa_4096']); return $this->state([ - 'public_key' => 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQC4VVsHFO5MxvCtAPyoKGANWyuwZ4fvllvFog5RJbfpDpw8etDFVGEXl+uRR8p79g9oV7MscoFo6HiWrJc4/4vlP665msjosILdIcbnuzMhvXnKisaGh9zflkpyR3KhUxoHxqYp2q8XtffjKKAHz1a8o7OUG6fwaKIqu+d0PoICZQ==', - 'fingerprint' => 'LQSzAAfAsbKpU94gojxXfjGjYcEv8UZIwyzwhcEr/aw', + 'public_key' => $key->toString('PKCS8'), + 'fingerprint' => $key->getFingerprint('sha256'), ]); } } From d4bf6bd46a4e50673891ba4a7951cd292811f8be Mon Sep 17 00:00:00 2001 From: DaneEveritt Date: Sun, 15 May 2022 17:30:57 -0400 Subject: [PATCH 039/458] Add test coverage and fix permissions mistake --- .../Remote/SftpAuthenticationController.php | 4 +- .../Client/ClientApiIntegrationTestCase.php | 30 --- .../SftpAuthenticationControllerTest.php | 250 ++++++++++++++++++ .../Traits/Integration/CreatesTestModels.php | 30 +++ 4 files changed, 282 insertions(+), 32 deletions(-) create mode 100644 tests/Integration/Api/Remote/SftpAuthenticationControllerTest.php diff --git a/app/Http/Controllers/Api/Remote/SftpAuthenticationController.php b/app/Http/Controllers/Api/Remote/SftpAuthenticationController.php index 7d42615078..8a68749068 100644 --- a/app/Http/Controllers/Api/Remote/SftpAuthenticationController.php +++ b/app/Http/Controllers/Api/Remote/SftpAuthenticationController.php @@ -62,7 +62,7 @@ public function __invoke(SftpAuthenticationFormRequest $request): JsonResponse } if (!$key || !$user->sshKeys()->where('fingerprint', $key->getFingerprint('sha256'))->exists()) { - $this->reject($request, false); + $this->reject($request, is_null($key)); } } @@ -70,7 +70,7 @@ public function __invoke(SftpAuthenticationFormRequest $request): JsonResponse return new JsonResponse([ 'server' => $server->uuid, - 'permissions' => $permissions ?? ['*'], + 'permissions' => $this->permissions->handle($server, $user), ]); } diff --git a/tests/Integration/Api/Client/ClientApiIntegrationTestCase.php b/tests/Integration/Api/Client/ClientApiIntegrationTestCase.php index 35e8d27191..03b80eba43 100644 --- a/tests/Integration/Api/Client/ClientApiIntegrationTestCase.php +++ b/tests/Integration/Api/Client/ClientApiIntegrationTestCase.php @@ -9,7 +9,6 @@ use InvalidArgumentException; use Pterodactyl\Models\Backup; use Pterodactyl\Models\Server; -use Pterodactyl\Models\Subuser; use Pterodactyl\Models\Database; use Pterodactyl\Models\Location; use Pterodactyl\Models\Schedule; @@ -88,35 +87,6 @@ protected function link($model, $append = null): string return $link . ($append ? '/' . ltrim($append, '/') : ''); } - /** - * Generates a user and a server for that user. If an array of permissions is passed it - * is assumed that the user is actually a subuser of the server. - * - * @param string[] $permissions - * - * @return array{\Pterodactyl\Models\User, \Pterodactyl\Models\Server} - */ - protected function generateTestAccount(array $permissions = []): array - { - /** @var \Pterodactyl\Models\User $user */ - $user = User::factory()->create(); - - if (empty($permissions)) { - return [$user, $this->createServerModel(['user_id' => $user->id])]; - } - - /** @var \Pterodactyl\Models\Server $server */ - $server = $this->createServerModel(); - - Subuser::query()->create([ - 'user_id' => $user->id, - 'server_id' => $server->id, - 'permissions' => $permissions, - ]); - - return [$user, $server]; - } - /** * Asserts that the data passed through matches the output of the data from the transformer. This * will remove the "relationships" key when performing the comparison. diff --git a/tests/Integration/Api/Remote/SftpAuthenticationControllerTest.php b/tests/Integration/Api/Remote/SftpAuthenticationControllerTest.php new file mode 100644 index 0000000000..4ef485db32 --- /dev/null +++ b/tests/Integration/Api/Remote/SftpAuthenticationControllerTest.php @@ -0,0 +1,250 @@ +generateTestAccount(); + + $user->update(['password' => password_hash('foobar', PASSWORD_DEFAULT)]); + + $this->user = $user; + $this->server = $server; + + $this->setAuthorization(); + } + + /** + * Test that a public key is validated correctly. + */ + public function testPublicKeyIsValidatedCorrectly() + { + $key = UserSSHKey::factory()->for($this->user)->create(); + + $this->postJson('/api/remote/sftp/auth', []) + ->assertUnprocessable() + ->assertJsonPath('errors.0.meta.source_field', 'username') + ->assertJsonPath('errors.0.meta.rule', 'required') + ->assertJsonPath('errors.1.meta.source_field', 'password') + ->assertJsonPath('errors.1.meta.rule', 'required'); + + $data = [ + 'type' => 'public_key', + 'username' => $this->getUsername(), + 'password' => $key->public_key, + ]; + + $this->postJson('/api/remote/sftp/auth', $data) + ->assertOk() + ->assertJsonPath('server', $this->server->uuid) + ->assertJsonPath('permissions', ['*']); + + $key->delete(); + $this->postJson('/api/remote/sftp/auth', $data)->assertForbidden(); + $this->postJson('/api/remote/sftp/auth', array_merge($data, ['type' => null]))->assertForbidden(); + } + + /** + * Test that an account password is validated correctly. + */ + public function testPasswordIsValidatedCorrectly() + { + $this->postJson('/api/remote/sftp/auth', [ + 'username' => $this->getUsername(), + 'password' => '', + ]) + ->assertUnprocessable() + ->assertJsonPath('errors.0.meta.source_field', 'password') + ->assertJsonPath('errors.0.meta.rule', 'required'); + + $this->postJson('/api/remote/sftp/auth', [ + 'username' => $this->getUsername(), + 'password' => 'wrong password', + ]) + ->assertForbidden(); + + $this->user->update(['password' => password_hash('foobar', PASSWORD_DEFAULT)]); + + $this->postJson('/api/remote/sftp/auth', [ + 'username' => $this->getUsername(), + 'password' => 'foobar', + ]) + ->assertOk(); + } + + /** + * Test that providing an invalid key and/or invalid username triggers the throttle on + * the endpoint. + * + * @dataProvider authorizationTypeDataProvider + */ + public function testUserIsThrottledIfInvalidCredentialsAreProvided(string $type) + { + for ($i = 0; $i <= 10; ++$i) { + $this->postJson('/api/remote/sftp/auth', [ + 'type' => 'public_key', + 'username' => $i % 2 === 0 ? $this->user->username : $this->getUsername(), + 'password' => 'invalid key', + ]) + ->assertStatus($i === 10 ? 429 : 403); + } + } + + /** + * Test that the user is not throttled so long as a valid public key is provided, even + * if it doesn't actually exist in the database for the user. + */ + public function testUserIsNotThrottledIfNoPublicKeyMatches() + { + for ($i = 0; $i <= 10; ++$i) { + $this->postJson('/api/remote/sftp/auth', [ + 'type' => 'public_key', + 'username' => $this->getUsername(), + 'password' => PrivateKey::createKey('Ed25519')->getPublicKey()->toString('OpenSSH'), + ]) + ->assertForbidden(); + } + } + + /** + * Test that a request is rejected if the credentials are valid but the username indicates + * a server on a different node. + * + * @dataProvider authorizationTypeDataProvider + */ + public function testRequestIsRejectedIfServerBelongsToDifferentNode(string $type) + { + $node2 = $this->createServerModel()->node; + + $this->setAuthorization($node2); + + $password = $type === 'public_key' + ? UserSSHKey::factory()->for($this->user)->create()->public_key + : 'foobar'; + + $this->postJson('/api/remote/sftp/auth', [ + 'type' => 'public_key', + 'username' => $this->getUsername(), + 'password' => $password, + ]) + ->assertForbidden(); + } + + public function testRequestIsDeniedIfUserLacksSftpPermission() + { + [$user, $server] = $this->generateTestAccount([Permission::ACTION_FILE_READ]); + + $user->update(['password' => password_hash('foobar', PASSWORD_DEFAULT)]); + + $this->setAuthorization($server->node); + + $this->postJson('/api/remote/sftp/auth', [ + 'username' => $user->username . '.' . $server->uuidShort, + 'password' => 'foobar', + ]) + ->assertForbidden() + ->assertJsonPath('errors.0.detail', 'You do not have permission to access SFTP for this server.'); + } + + /** + * @dataProvider serverStateDataProvider + */ + public function testInvalidServerStateReturnsConflictError(string $status) + { + $this->server->update(['status' => $status]); + + $this->postJson('/api/remote/sftp/auth', ['username' => $this->getUsername(), 'password' => 'foobar']) + ->assertStatus(409); + } + + /** + * Test that permissions are returned for the user account correctly. + */ + public function testUserPermissionsAreReturnedCorrectly() + { + [$user, $server] = $this->generateTestAccount([Permission::ACTION_FILE_READ, Permission::ACTION_FILE_SFTP]); + + $user->update(['password' => password_hash('foobar', PASSWORD_DEFAULT)]); + + $this->setAuthorization($server->node); + + $data = [ + 'username' => $user->username . '.' . $server->uuidShort, + 'password' => 'foobar', + ]; + + $this->postJson('/api/remote/sftp/auth', $data) + ->assertOk() + ->assertJsonPath('permissions', [Permission::ACTION_FILE_READ, Permission::ACTION_FILE_SFTP]); + + $user->update(['root_admin' => true]); + + $this->postJson('/api/remote/sftp/auth', $data) + ->assertOk() + ->assertJsonPath('permissions.0', '*'); + + $this->setAuthorization(); + $data['username'] = $user->username . '.' . $this->server->uuidShort; + + $this->post('/api/remote/sftp/auth', $data) + ->assertOk() + ->assertJsonPath('permissions.0', '*'); + + $user->update(['root_admin' => false]); + $this->post('/api/remote/sftp/auth', $data)->assertForbidden(); + } + + public function authorizationTypeDataProvider(): array + { + return [ + 'password auth' => ['password'], + 'public key auth' => ['public_key'], + ]; + } + + public function serverStateDataProvider(): array + { + return [ + 'installing' => [Server::STATUS_INSTALLING], + 'suspended' => [Server::STATUS_SUSPENDED], + 'restoring a backup' => [Server::STATUS_RESTORING_BACKUP], + ]; + } + + /** + * Returns the username for connecting to SFTP. + */ + protected function getUsername(bool $long = false): string + { + return $this->user->username . '.' . ($long ? $this->server->uuid : $this->server->uuidShort); + } + + /** + * Sets the authorization header for the rest of the test. + */ + protected function setAuthorization(Node $node = null): void + { + $node = $node ?? $this->server->node; + + $this->withHeader('Authorization', 'Bearer ' . $node->daemon_token_id . '.' . decrypt($node->daemon_token)); + } +} diff --git a/tests/Traits/Integration/CreatesTestModels.php b/tests/Traits/Integration/CreatesTestModels.php index 0885ccc5af..51e4b76aab 100644 --- a/tests/Traits/Integration/CreatesTestModels.php +++ b/tests/Traits/Integration/CreatesTestModels.php @@ -7,6 +7,7 @@ use Pterodactyl\Models\Node; use Pterodactyl\Models\User; use Pterodactyl\Models\Server; +use Pterodactyl\Models\Subuser; use Pterodactyl\Models\Location; use Pterodactyl\Models\Allocation; @@ -76,6 +77,35 @@ public function createServerModel(array $attributes = []) ]); } + /** + * Generates a user and a server for that user. If an array of permissions is passed it + * is assumed that the user is actually a subuser of the server. + * + * @param string[] $permissions + * + * @return array{\Pterodactyl\Models\User, \Pterodactyl\Models\Server} + */ + public function generateTestAccount(array $permissions = []): array + { + /** @var \Pterodactyl\Models\User $user */ + $user = User::factory()->create(); + + if (empty($permissions)) { + return [$user, $this->createServerModel(['user_id' => $user->id])]; + } + + /** @var \Pterodactyl\Models\Server $server */ + $server = $this->createServerModel(); + + Subuser::query()->create([ + 'user_id' => $user->id, + 'server_id' => $server->id, + 'permissions' => $permissions, + ]); + + return [$user, $server]; + } + /** * Clones a given egg allowing us to make modifications that don't affect other * tests that rely on the egg existing in the correct state. From d0b6ae00dccb04a53d224446393246dcd6109474 Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 21 May 2022 22:50:07 +0300 Subject: [PATCH 040/458] fix: update Paper API (#4080) Paper api endpoints have been updated to api.papermc.io/v2 from the old papermc.io/api/v2 --- database/Seeders/eggs/minecraft/egg-paper.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/database/Seeders/eggs/minecraft/egg-paper.json b/database/Seeders/eggs/minecraft/egg-paper.json index d6db44fd58..2467c464f8 100644 --- a/database/Seeders/eggs/minecraft/egg-paper.json +++ b/database/Seeders/eggs/minecraft/egg-paper.json @@ -4,7 +4,7 @@ "version": "PTDL_v2", "update_url": null }, - "exported_at": "2022-05-07T17:35:09-04:00", + "exported_at": "2022-05-21T10:26:00-04:00", "name": "Paper", "author": "parker@pterodactyl.io", "description": "High performance Spigot fork that aims to fix gameplay and mechanics inconsistencies.", @@ -29,7 +29,7 @@ }, "scripts": { "installation": { - "script": "#!\/bin\/ash\r\n# Paper Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\nPROJECT=paper\r\n\r\nif [ -n \"${DL_PATH}\" ]; then\r\n\techo -e \"Using supplied download url: ${DL_PATH}\"\r\n\tDOWNLOAD_URL=`eval echo $(echo ${DL_PATH} | sed -e 's\/{{\/${\/g' -e 's\/}}\/}\/g')`\r\nelse\r\n\tVER_EXISTS=`curl -s https:\/\/papermc.io\/api\/v2\/projects\/${PROJECT} | jq -r --arg VERSION $MINECRAFT_VERSION '.versions[] | contains($VERSION)' | grep -m1 true`\r\n\tLATEST_VERSION=`curl -s https:\/\/papermc.io\/api\/v2\/projects\/${PROJECT} | jq -r '.versions' | jq -r '.[-1]'`\r\n\r\n\tif [ \"${VER_EXISTS}\" == \"true\" ]; then\r\n\t\techo -e \"Version is valid. Using version ${MINECRAFT_VERSION}\"\r\n\telse\r\n\t\techo -e \"Specified version not found. Defaulting to the latest ${PROJECT} version\"\r\n\t\tMINECRAFT_VERSION=${LATEST_VERSION}\r\n\tfi\r\n\r\n\tBUILD_EXISTS=`curl -s https:\/\/papermc.io\/api\/v2\/projects\/${PROJECT}\/versions\/${MINECRAFT_VERSION} | jq -r --arg BUILD ${BUILD_NUMBER} '.builds[] | tostring | contains($BUILD)' | grep -m1 true`\r\n\tLATEST_BUILD=`curl -s https:\/\/papermc.io\/api\/v2\/projects\/${PROJECT}\/versions\/${MINECRAFT_VERSION} | jq -r '.builds' | jq -r '.[-1]'`\r\n\r\n\tif [ \"${BUILD_EXISTS}\" == \"true\" ]; then\r\n\t\techo -e \"Build is valid for version ${MINECRAFT_VERSION}. Using build ${BUILD_NUMBER}\"\r\n\telse\r\n\t\techo -e \"Using the latest ${PROJECT} build for version ${MINECRAFT_VERSION}\"\r\n\t\tBUILD_NUMBER=${LATEST_BUILD}\r\n\tfi\r\n\r\n\tJAR_NAME=${PROJECT}-${MINECRAFT_VERSION}-${BUILD_NUMBER}.jar\r\n\r\n\techo \"Version being downloaded\"\r\n\techo -e \"MC Version: ${MINECRAFT_VERSION}\"\r\n\techo -e \"Build: ${BUILD_NUMBER}\"\r\n\techo -e \"JAR Name of Build: ${JAR_NAME}\"\r\n\tDOWNLOAD_URL=https:\/\/papermc.io\/api\/v2\/projects\/${PROJECT}\/versions\/${MINECRAFT_VERSION}\/builds\/${BUILD_NUMBER}\/downloads\/${JAR_NAME}\r\nfi\r\n\r\ncd \/mnt\/server\r\n\r\necho -e \"Running curl -o ${SERVER_JARFILE} ${DOWNLOAD_URL}\"\r\n\r\nif [ -f ${SERVER_JARFILE} ]; then\r\n\tmv ${SERVER_JARFILE} ${SERVER_JARFILE}.old\r\nfi\r\n\r\ncurl -o ${SERVER_JARFILE} ${DOWNLOAD_URL}\r\n\r\nif [ ! -f server.properties ]; then\r\n echo -e \"Downloading MC server.properties\"\r\n curl -o server.properties https:\/\/raw.githubusercontent.com\/parkervcp\/eggs\/master\/minecraft\/java\/server.properties\r\nfi", + "script": "#!\/bin\/ash\r\n# Paper Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\nPROJECT=paper\r\n\r\nif [ -n \"${DL_PATH}\" ]; then\r\n\techo -e \"Using supplied download url: ${DL_PATH}\"\r\n\tDOWNLOAD_URL=`eval echo $(echo ${DL_PATH} | sed -e 's\/{{\/${\/g' -e 's\/}}\/}\/g')`\r\nelse\r\n\tVER_EXISTS=`curl -s https:\/\/api.papermc.io\/v2\/projects\/${PROJECT} | jq -r --arg VERSION $MINECRAFT_VERSION '.versions[] | contains($VERSION)' | grep -m1 true`\r\n\tLATEST_VERSION=`curl -s https:\/\/api.papermc.io\/v2\/projects\/${PROJECT} | jq -r '.versions' | jq -r '.[-1]'`\r\n\r\n\tif [ \"${VER_EXISTS}\" == \"true\" ]; then\r\n\t\techo -e \"Version is valid. Using version ${MINECRAFT_VERSION}\"\r\n\telse\r\n\t\techo -e \"Specified version not found. Defaulting to the latest ${PROJECT} version\"\r\n\t\tMINECRAFT_VERSION=${LATEST_VERSION}\r\n\tfi\r\n\r\n\tBUILD_EXISTS=`curl -s https:\/\/api.papermc.io\/v2\/projects\/${PROJECT}\/versions\/${MINECRAFT_VERSION} | jq -r --arg BUILD ${BUILD_NUMBER} '.builds[] | tostring | contains($BUILD)' | grep -m1 true`\r\n\tLATEST_BUILD=`curl -s https:\/\/api.papermc.io\/v2\/projects\/${PROJECT}\/versions\/${MINECRAFT_VERSION} | jq -r '.builds' | jq -r '.[-1]'`\r\n\r\n\tif [ \"${BUILD_EXISTS}\" == \"true\" ]; then\r\n\t\techo -e \"Build is valid for version ${MINECRAFT_VERSION}. Using build ${BUILD_NUMBER}\"\r\n\telse\r\n\t\techo -e \"Using the latest ${PROJECT} build for version ${MINECRAFT_VERSION}\"\r\n\t\tBUILD_NUMBER=${LATEST_BUILD}\r\n\tfi\r\n\r\n\tJAR_NAME=${PROJECT}-${MINECRAFT_VERSION}-${BUILD_NUMBER}.jar\r\n\r\n\techo \"Version being downloaded\"\r\n\techo -e \"MC Version: ${MINECRAFT_VERSION}\"\r\n\techo -e \"Build: ${BUILD_NUMBER}\"\r\n\techo -e \"JAR Name of Build: ${JAR_NAME}\"\r\n\tDOWNLOAD_URL=https:\/\/api.papermc.io\/v2\/projects\/${PROJECT}\/versions\/${MINECRAFT_VERSION}\/builds\/${BUILD_NUMBER}\/downloads\/${JAR_NAME}\r\nfi\r\n\r\ncd \/mnt\/server\r\n\r\necho -e \"Running curl -o ${SERVER_JARFILE} ${DOWNLOAD_URL}\"\r\n\r\nif [ -f ${SERVER_JARFILE} ]; then\r\n\tmv ${SERVER_JARFILE} ${SERVER_JARFILE}.old\r\nfi\r\n\r\ncurl -o ${SERVER_JARFILE} ${DOWNLOAD_URL}\r\n\r\nif [ ! -f server.properties ]; then\r\n echo -e \"Downloading MC server.properties\"\r\n curl -o server.properties https:\/\/raw.githubusercontent.com\/parkervcp\/eggs\/master\/minecraft\/java\/server.properties\r\nfi", "container": "ghcr.io\/pterodactyl\/installers:alpine", "entrypoint": "ash" } From 05f41a2ca8509c9867bd35613f8ec871eed1f441 Mon Sep 17 00:00:00 2001 From: DaneEveritt Date: Sat, 21 May 2022 16:58:04 -0400 Subject: [PATCH 041/458] Don't trim strings on file manager endpoints; ref #4081 --- app/Providers/RouteServiceProvider.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index 0ccebcc64c..188509fce0 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -6,10 +6,13 @@ use Illuminate\Support\Facades\Route; use Illuminate\Cache\RateLimiting\Limit; use Illuminate\Support\Facades\RateLimiter; +use Pterodactyl\Http\Middleware\TrimStrings; use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider; class RouteServiceProvider extends ServiceProvider { + protected const FILE_PATH_REGEX = '/^\/api\/client\/servers\/([a-z0-9-]{36})\/files(\/?$|\/(.)*$)/i'; + /** * Define your route model bindings, pattern filters, etc. */ @@ -17,6 +20,12 @@ public function boot() { $this->configureRateLimiting(); + // Disable trimming string values when requesting file information — it isn't helpful + // and messes up the ability to actually open a directory that ends with a space. + TrimStrings::skipWhen(function (Request $request) { + return preg_match(self::FILE_PATH_REGEX, $request->getPathInfo()) === 1; + }); + $this->routes(function () { Route::middleware(['web', 'csrf'])->group(function () { Route::middleware('auth')->group(base_path('routes/base.php')); From f1235c7f882cdd423d5ca2898336bf4a0d90f80f Mon Sep 17 00:00:00 2001 From: DaneEveritt Date: Sat, 21 May 2022 17:02:40 -0400 Subject: [PATCH 042/458] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 78a426356c..613a63dcda 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ using these eggs should be updated to account for the new format. * Allocations cannot be deleted from a server by a user if the server is configured with an `allocation_limit` set to `0`. * The Java Version modal no longer shows a dropdown and update option to users that do not have permission to make those changes. * The Java Version modal now correctly returns only the images available to the server's selected Egg. +* Fixes leading and trailing spaces being removed from variable values on file manager endpoints, causing errors when trying to perform actions against certain files and folders. ### Changed * Forces HTTPS on URLs when the `APP_URL` value is set and includes `https://` within the URL. This addresses proxy misconfiguration issues that would cause URLs to be generated incorrectly. From e313dff674c3ca952da90e8f2c9951eb2fcb9752 Mon Sep 17 00:00:00 2001 From: DaneEveritt Date: Sun, 22 May 2022 14:10:01 -0400 Subject: [PATCH 043/458] Massively simplify API binding logic Changes the API internals to use normal Laravel binding which automatically supports nested-models and can determine their relationships. This removes a lot of confusingly complex internal logic and replaces it with standard Laravel code. This also removes a deprecated "getModel" method and fully replaces it with a "parameter" method that does stricter type-checking. --- .../Locations/LocationController.php | 12 +-- .../Api/Application/Nests/EggController.php | 28 +----- .../Api/Application/Nests/NestController.php | 4 +- .../Servers/DatabaseController.php | 6 +- .../Servers/ExternalServerController.php | 7 +- .../Application/Servers/ServerController.php | 4 +- .../Servers/ServerDetailsController.php | 8 +- .../Application/Servers/StartupController.php | 4 +- .../Users/ExternalUserController.php | 7 +- .../Api/Client/Servers/ScheduleController.php | 3 +- .../Remote/SftpAuthenticationController.php | 2 +- app/Http/Kernel.php | 7 +- .../Middleware/Api/ApiSubstituteBindings.php | 87 ----------------- .../Client/SubstituteClientApiBindings.php | 62 ------------ .../Api/Client/SubstituteClientBindings.php | 36 +++++++ app/Http/Middleware/Api/IsValidJson.php | 9 +- .../Allocations/DeleteAllocationRequest.php | 20 ---- .../Allocations/GetAllocationsRequest.php | 12 --- .../Api/Application/ApplicationApiRequest.php | 97 ++++--------------- .../Locations/DeleteLocationRequest.php | 11 --- .../Locations/GetLocationRequest.php | 11 --- .../Locations/UpdateLocationRequest.php | 10 -- .../Application/Nests/Eggs/GetEggRequest.php | 10 -- .../Application/Nodes/DeleteNodeRequest.php | 12 --- .../Api/Application/Nodes/GetNodeRequest.php | 11 --- .../Application/Nodes/UpdateNodeRequest.php | 4 +- .../Databases/GetServerDatabaseRequest.php | 11 --- .../Servers/GetExternalServerRequest.php | 34 ------- .../UpdateServerBuildConfigurationRequest.php | 2 +- .../Servers/UpdateServerDetailsRequest.php | 2 +- .../Servers/UpdateServerStartupRequest.php | 2 +- .../Application/Users/DeleteUserRequest.php | 11 --- .../Users/GetExternalUserRequest.php | 34 ------- .../Application/Users/UpdateUserRequest.php | 2 +- .../Databases/DeleteDatabaseRequest.php | 7 -- .../Servers/Files/DownloadFileRequest.php | 2 +- app/Models/Allocation.php | 8 ++ app/Models/Database.php | 33 +++++++ app/Models/Location.php | 8 ++ app/Models/Model.php | 14 +++ app/Models/Schedule.php | 8 ++ app/Models/Server.php | 84 ++++++++++++---- app/Models/Task.php | 8 ++ app/Models/User.php | 2 +- app/Models/UserSSHKey.php | 2 + app/Providers/RouteServiceProvider.php | 12 ++- routes/api-application.php | 56 +++++------ .../Location/LocationControllerTest.php | 12 --- .../Application/Nests/EggControllerTest.php | 13 --- .../Application/Nests/NestControllerTest.php | 13 --- .../Users/ExternalUserControllerTest.php | 12 --- .../Application/Users/UserControllerTest.php | 12 --- .../Http/IntegrationJsonRequestAssertions.php | 2 +- 53 files changed, 288 insertions(+), 602 deletions(-) delete mode 100644 app/Http/Middleware/Api/ApiSubstituteBindings.php delete mode 100644 app/Http/Middleware/Api/Client/SubstituteClientApiBindings.php create mode 100644 app/Http/Middleware/Api/Client/SubstituteClientBindings.php diff --git a/app/Http/Controllers/Api/Application/Locations/LocationController.php b/app/Http/Controllers/Api/Application/Locations/LocationController.php index b7e82396a8..e8f166aba2 100644 --- a/app/Http/Controllers/Api/Application/Locations/LocationController.php +++ b/app/Http/Controllers/Api/Application/Locations/LocationController.php @@ -75,9 +75,9 @@ public function index(GetLocationsRequest $request): array /** * Return a single location. */ - public function view(GetLocationRequest $request): array + public function view(GetLocationRequest $request, Location $location): array { - return $this->fractal->item($request->getModel(Location::class)) + return $this->fractal->item($location) ->transformWith($this->getTransformer(LocationTransformer::class)) ->toArray(); } @@ -108,9 +108,9 @@ public function store(StoreLocationRequest $request): JsonResponse * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function update(UpdateLocationRequest $request): array + public function update(UpdateLocationRequest $request, Location $location): array { - $location = $this->updateService->handle($request->getModel(Location::class), $request->validated()); + $location = $this->updateService->handle($location, $request->validated()); return $this->fractal->item($location) ->transformWith($this->getTransformer(LocationTransformer::class)) @@ -122,9 +122,9 @@ public function update(UpdateLocationRequest $request): array * * @throws \Pterodactyl\Exceptions\Service\Location\HasActiveNodesException */ - public function delete(DeleteLocationRequest $request): Response + public function delete(DeleteLocationRequest $request, Location $location): Response { - $this->deletionService->handle($request->getModel(Location::class)); + $this->deletionService->handle($location); return response('', 204); } diff --git a/app/Http/Controllers/Api/Application/Nests/EggController.php b/app/Http/Controllers/Api/Application/Nests/EggController.php index 1eb596f990..83c3f77a0e 100644 --- a/app/Http/Controllers/Api/Application/Nests/EggController.php +++ b/app/Http/Controllers/Api/Application/Nests/EggController.php @@ -4,7 +4,6 @@ use Pterodactyl\Models\Egg; use Pterodactyl\Models\Nest; -use Pterodactyl\Contracts\Repository\EggRepositoryInterface; use Pterodactyl\Transformers\Api\Application\EggTransformer; use Pterodactyl\Http\Requests\Api\Application\Nests\Eggs\GetEggRequest; use Pterodactyl\Http\Requests\Api\Application\Nests\Eggs\GetEggsRequest; @@ -12,31 +11,12 @@ class EggController extends ApplicationApiController { - /** - * @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface - */ - private $repository; - - /** - * EggController constructor. - */ - public function __construct(EggRepositoryInterface $repository) - { - parent::__construct(); - - $this->repository = $repository; - } - /** * Return all eggs that exist for a given nest. */ - public function index(GetEggsRequest $request): array + public function index(GetEggsRequest $request, Nest $nest): array { - $eggs = $this->repository->findWhere([ - ['nest_id', '=', $request->getModel(Nest::class)->id], - ]); - - return $this->fractal->collection($eggs) + return $this->fractal->collection($nest->eggs) ->transformWith($this->getTransformer(EggTransformer::class)) ->toArray(); } @@ -44,9 +24,9 @@ public function index(GetEggsRequest $request): array /** * Return a single egg that exists on the specified nest. */ - public function view(GetEggRequest $request): array + public function view(GetEggRequest $request, Nest $nest, Egg $egg): array { - return $this->fractal->item($request->getModel(Egg::class)) + return $this->fractal->item($egg) ->transformWith($this->getTransformer(EggTransformer::class)) ->toArray(); } diff --git a/app/Http/Controllers/Api/Application/Nests/NestController.php b/app/Http/Controllers/Api/Application/Nests/NestController.php index b66872d231..232bf026c1 100644 --- a/app/Http/Controllers/Api/Application/Nests/NestController.php +++ b/app/Http/Controllers/Api/Application/Nests/NestController.php @@ -40,9 +40,9 @@ public function index(GetNestsRequest $request): array /** * Return information about a single Nest model. */ - public function view(GetNestsRequest $request): array + public function view(GetNestsRequest $request, Nest $nest): array { - return $this->fractal->item($request->getModel(Nest::class)) + return $this->fractal->item($nest) ->transformWith($this->getTransformer(NestTransformer::class)) ->toArray(); } diff --git a/app/Http/Controllers/Api/Application/Servers/DatabaseController.php b/app/Http/Controllers/Api/Application/Servers/DatabaseController.php index fc17562306..2b9564728f 100644 --- a/app/Http/Controllers/Api/Application/Servers/DatabaseController.php +++ b/app/Http/Controllers/Api/Application/Servers/DatabaseController.php @@ -105,12 +105,10 @@ public function store(StoreServerDatabaseRequest $request, Server $server): Json /** * Handle a request to delete a specific server database from the Panel. - * - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function delete(ServerDatabaseWriteRequest $request): Response + public function delete(ServerDatabaseWriteRequest $request, Server $server, Database $database): Response { - $this->databaseManagementService->delete($request->getModel(Database::class)); + $this->databaseManagementService->delete($database); return response('', 204); } diff --git a/app/Http/Controllers/Api/Application/Servers/ExternalServerController.php b/app/Http/Controllers/Api/Application/Servers/ExternalServerController.php index 0cecb977a9..869472f724 100644 --- a/app/Http/Controllers/Api/Application/Servers/ExternalServerController.php +++ b/app/Http/Controllers/Api/Application/Servers/ExternalServerController.php @@ -2,6 +2,7 @@ namespace Pterodactyl\Http\Controllers\Api\Application\Servers; +use Pterodactyl\Models\Server; use Pterodactyl\Transformers\Api\Application\ServerTransformer; use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController; use Pterodactyl\Http\Requests\Api\Application\Servers\GetExternalServerRequest; @@ -11,9 +12,11 @@ class ExternalServerController extends ApplicationApiController /** * Retrieve a specific server from the database using its external ID. */ - public function index(GetExternalServerRequest $request): array + public function index(GetExternalServerRequest $request, string $external_id): array { - return $this->fractal->item($request->getServerModel()) + $server = Server::query()->where('external_id', $external_id)->firstOrFail(); + + return $this->fractal->item($server) ->transformWith($this->getTransformer(ServerTransformer::class)) ->toArray(); } diff --git a/app/Http/Controllers/Api/Application/Servers/ServerController.php b/app/Http/Controllers/Api/Application/Servers/ServerController.php index fb1871f690..a52b46d579 100644 --- a/app/Http/Controllers/Api/Application/Servers/ServerController.php +++ b/app/Http/Controllers/Api/Application/Servers/ServerController.php @@ -86,9 +86,9 @@ public function store(StoreServerRequest $request): JsonResponse /** * Show a single server transformed for the application API. */ - public function view(GetServerRequest $request): array + public function view(GetServerRequest $request, Server $server): array { - return $this->fractal->item($request->getModel(Server::class)) + return $this->fractal->item($server) ->transformWith($this->getTransformer(ServerTransformer::class)) ->toArray(); } diff --git a/app/Http/Controllers/Api/Application/Servers/ServerDetailsController.php b/app/Http/Controllers/Api/Application/Servers/ServerDetailsController.php index 06bbf6bca5..bcecf277c0 100644 --- a/app/Http/Controllers/Api/Application/Servers/ServerDetailsController.php +++ b/app/Http/Controllers/Api/Application/Servers/ServerDetailsController.php @@ -42,14 +42,14 @@ public function __construct( * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function details(UpdateServerDetailsRequest $request): array + public function details(UpdateServerDetailsRequest $request, Server $server): array { - $server = $this->detailsModificationService->returnUpdatedModel()->handle( - $request->getModel(Server::class), + $updated = $this->detailsModificationService->returnUpdatedModel()->handle( + $server, $request->validated() ); - return $this->fractal->item($server) + return $this->fractal->item($updated) ->transformWith($this->getTransformer(ServerTransformer::class)) ->toArray(); } diff --git a/app/Http/Controllers/Api/Application/Servers/StartupController.php b/app/Http/Controllers/Api/Application/Servers/StartupController.php index e8300754fe..6c854102b7 100644 --- a/app/Http/Controllers/Api/Application/Servers/StartupController.php +++ b/app/Http/Controllers/Api/Application/Servers/StartupController.php @@ -34,11 +34,11 @@ public function __construct(StartupModificationService $modificationService) * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function index(UpdateServerStartupRequest $request): array + public function index(UpdateServerStartupRequest $request, Server $server): array { $server = $this->modificationService ->setUserLevel(User::USER_LEVEL_ADMIN) - ->handle($request->getModel(Server::class), $request->validated()); + ->handle($server, $request->validated()); return $this->fractal->item($server) ->transformWith($this->getTransformer(ServerTransformer::class)) diff --git a/app/Http/Controllers/Api/Application/Users/ExternalUserController.php b/app/Http/Controllers/Api/Application/Users/ExternalUserController.php index a253b6e195..2a8f4f07e7 100644 --- a/app/Http/Controllers/Api/Application/Users/ExternalUserController.php +++ b/app/Http/Controllers/Api/Application/Users/ExternalUserController.php @@ -2,6 +2,7 @@ namespace Pterodactyl\Http\Controllers\Api\Application\Users; +use Pterodactyl\Models\User; use Pterodactyl\Transformers\Api\Application\UserTransformer; use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController; use Pterodactyl\Http\Requests\Api\Application\Users\GetExternalUserRequest; @@ -11,9 +12,11 @@ class ExternalUserController extends ApplicationApiController /** * Retrieve a specific user from the database using their external ID. */ - public function index(GetExternalUserRequest $request): array + public function index(GetExternalUserRequest $request, string $external_id): array { - return $this->fractal->item($request->getUserModel()) + $user = User::query()->where('external_id', $external_id)->firstOrFail(); + + return $this->fractal->item($user) ->transformWith($this->getTransformer(UserTransformer::class)) ->toArray(); } diff --git a/app/Http/Controllers/Api/Client/Servers/ScheduleController.php b/app/Http/Controllers/Api/Client/Servers/ScheduleController.php index 0bbc6bd733..3e9b822bbd 100644 --- a/app/Http/Controllers/Api/Client/Servers/ScheduleController.php +++ b/app/Http/Controllers/Api/Client/Servers/ScheduleController.php @@ -52,8 +52,7 @@ public function __construct(ScheduleRepository $repository, ProcessScheduleServi */ public function index(ViewScheduleRequest $request, Server $server) { - $schedules = $server->schedule; - $schedules->loadMissing('tasks'); + $schedules = $server->schedules->loadMissing('tasks'); return $this->fractal->collection($schedules) ->transformWith($this->getTransformer(ScheduleTransformer::class)) diff --git a/app/Http/Controllers/Api/Remote/SftpAuthenticationController.php b/app/Http/Controllers/Api/Remote/SftpAuthenticationController.php index 8a68749068..ceaa83ebee 100644 --- a/app/Http/Controllers/Api/Remote/SftpAuthenticationController.php +++ b/app/Http/Controllers/Api/Remote/SftpAuthenticationController.php @@ -2,8 +2,8 @@ namespace Pterodactyl\Http\Controllers\Api\Remote; -use Pterodactyl\Models\User; use Illuminate\Http\Request; +use Pterodactyl\Models\User; use Pterodactyl\Models\Server; use Illuminate\Http\JsonResponse; use Pterodactyl\Models\Permission; diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 2ff7a6260a..3e9f1dd197 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -24,7 +24,6 @@ use Pterodactyl\Http\Middleware\RedirectIfAuthenticated; use Illuminate\Auth\Middleware\AuthenticateWithBasicAuth; use Pterodactyl\Http\Middleware\Api\AuthenticateIPAccess; -use Pterodactyl\Http\Middleware\Api\ApiSubstituteBindings; use Illuminate\Foundation\Http\Middleware\ValidatePostSize; use Pterodactyl\Http\Middleware\Api\HandleStatelessRequest; use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse; @@ -32,7 +31,7 @@ use Pterodactyl\Http\Middleware\RequireTwoFactorAuthentication; use Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode; use Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull; -use Pterodactyl\Http\Middleware\Api\Client\SubstituteClientApiBindings; +use Pterodactyl\Http\Middleware\Api\Client\SubstituteClientBindings; use Pterodactyl\Http\Middleware\Api\Application\AuthenticateApplicationUser; class Kernel extends HttpKernel @@ -75,13 +74,13 @@ class Kernel extends HttpKernel VerifyCsrfToken::class, ], 'application-api' => [ - ApiSubstituteBindings::class, + SubstituteBindings::class, 'api..key:' . ApiKey::TYPE_APPLICATION, AuthenticateApplicationUser::class, AuthenticateIPAccess::class, ], 'client-api' => [ - SubstituteClientApiBindings::class, + SubstituteClientBindings::class, 'api..key:' . ApiKey::TYPE_ACCOUNT, AuthenticateIPAccess::class, // This is perhaps a little backwards with the Client API, but logically you'd be unable diff --git a/app/Http/Middleware/Api/ApiSubstituteBindings.php b/app/Http/Middleware/Api/ApiSubstituteBindings.php deleted file mode 100644 index 7ade7452a0..0000000000 --- a/app/Http/Middleware/Api/ApiSubstituteBindings.php +++ /dev/null @@ -1,87 +0,0 @@ - Allocation::class, - 'database' => Database::class, - 'egg' => Egg::class, - 'location' => Location::class, - 'nest' => Nest::class, - 'node' => Node::class, - 'server' => Server::class, - 'user' => User::class, - ]; - - /** - * @var \Illuminate\Routing\Router - */ - protected $router; - - /** - * Perform substitution of route parameters without triggering - * a 404 error if a model is not found. - * - * @param \Illuminate\Http\Request $request - * - * @return mixed - */ - public function handle($request, Closure $next) - { - $route = $request->route(); - - foreach (self::$mappings as $key => $model) { - if (!is_null($this->router->getBindingCallback($key))) { - continue; - } - - $this->router->model($key, $model, function () use ($request) { - $request->attributes->set('is_missing_model', true); - }); - } - - $this->router->substituteBindings($route); - - // Attempt to resolve bindings for this route. If one of the models - // cannot be resolved do not immediately return a 404 error. Set a request - // attribute that can be checked in the base API request class to only - // trigger a 404 after validating that the API key making the request is valid - // and even has permission to access the requested resource. - try { - $this->router->substituteImplicitBindings($route); - } catch (ModelNotFoundException $exception) { - $request->attributes->set('is_missing_model', true); - } - - return $next($request); - } - - /** - * Return the registered mappings. - * - * @return array - */ - public static function getMappings() - { - return self::$mappings; - } -} diff --git a/app/Http/Middleware/Api/Client/SubstituteClientApiBindings.php b/app/Http/Middleware/Api/Client/SubstituteClientApiBindings.php deleted file mode 100644 index 31590f28a3..0000000000 --- a/app/Http/Middleware/Api/Client/SubstituteClientApiBindings.php +++ /dev/null @@ -1,62 +0,0 @@ -router->bind('server', function ($value) use ($request) { - try { - $column = 'uuidShort'; - if (preg_match('/^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i', $value)) { - $column = 'uuid'; - } - - return Container::getInstance()->make(ServerRepositoryInterface::class)->findFirstWhere([ - [$column, '=', $value], - ]); - } catch (RecordNotFoundException $ex) { - $request->attributes->set('is_missing_model', true); - - return null; - } - }); - - $this->router->bind('database', function ($value) { - $id = Container::getInstance()->make(HashidsInterface::class)->decodeFirst($value); - - return Database::query()->where('id', $id)->firstOrFail(); - }); - - $this->router->bind('backup', function ($value) { - return Backup::query()->where('uuid', $value)->firstOrFail(); - }); - - $this->router->bind('user', function ($value) { - return User::query()->where('uuid', $value)->firstOrFail(); - }); - - return parent::handle($request, $next); - } -} diff --git a/app/Http/Middleware/Api/Client/SubstituteClientBindings.php b/app/Http/Middleware/Api/Client/SubstituteClientBindings.php new file mode 100644 index 0000000000..38d87bf2f4 --- /dev/null +++ b/app/Http/Middleware/Api/Client/SubstituteClientBindings.php @@ -0,0 +1,36 @@ +router->bind('server', function ($value) { + return Server::query()->where(strlen($value) === 8 ? 'uuidShort' : 'uuid', $value)->firstOrFail(); + }); + + $this->router->bind('user', function ($value, $route) { + /** @var \Pterodactyl\Models\Subuser $match */ + $match = $route->parameter('server') + ->subusers() + ->whereRelation('user', 'uuid', '=', $value) + ->firstOrFail(); + + return $match->user; + }); + + return parent::handle($request, $next); + } +} diff --git a/app/Http/Middleware/Api/IsValidJson.php b/app/Http/Middleware/Api/IsValidJson.php index c3c8d6c850..5f53b097d6 100644 --- a/app/Http/Middleware/Api/IsValidJson.php +++ b/app/Http/Middleware/Api/IsValidJson.php @@ -3,6 +3,7 @@ namespace Pterodactyl\Http\Middleware\Api; use Closure; +use JsonException; use Illuminate\Http\Request; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; @@ -18,10 +19,10 @@ class IsValidJson public function handle(Request $request, Closure $next) { if ($request->isJson() && !empty($request->getContent())) { - json_decode($request->getContent(), true); - - if (json_last_error() !== JSON_ERROR_NONE) { - throw new BadRequestHttpException(sprintf('The JSON data passed in the request appears to be malformed. err_code: %d err_message: "%s"', json_last_error(), json_last_error_msg())); + try { + json_decode($request->getContent(), true, 512, JSON_THROW_ON_ERROR); + } catch (JsonException $exception) { + throw new BadRequestHttpException('The JSON data passed in the request appears to be malformed: ' . $exception->getMessage()); } } diff --git a/app/Http/Requests/Api/Application/Allocations/DeleteAllocationRequest.php b/app/Http/Requests/Api/Application/Allocations/DeleteAllocationRequest.php index 9fab4e2c37..9005348589 100644 --- a/app/Http/Requests/Api/Application/Allocations/DeleteAllocationRequest.php +++ b/app/Http/Requests/Api/Application/Allocations/DeleteAllocationRequest.php @@ -2,8 +2,6 @@ namespace Pterodactyl\Http\Requests\Api\Application\Allocations; -use Pterodactyl\Models\Node; -use Pterodactyl\Models\Allocation; use Pterodactyl\Services\Acl\Api\AdminAcl; use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest; @@ -18,22 +16,4 @@ class DeleteAllocationRequest extends ApplicationApiRequest * @var int */ protected $permission = AdminAcl::WRITE; - - /** - * Determine if the requested allocation exists and belongs to the node that - * is being passed in the URL. - */ - public function resourceExists(): bool - { - $node = $this->route()->parameter('node'); - $allocation = $this->route()->parameter('allocation'); - - if ($node instanceof Node && $node->exists) { - if ($allocation instanceof Allocation && $allocation->exists && $allocation->node_id === $node->id) { - return true; - } - } - - return false; - } } diff --git a/app/Http/Requests/Api/Application/Allocations/GetAllocationsRequest.php b/app/Http/Requests/Api/Application/Allocations/GetAllocationsRequest.php index 524e201e1c..50afcf9600 100644 --- a/app/Http/Requests/Api/Application/Allocations/GetAllocationsRequest.php +++ b/app/Http/Requests/Api/Application/Allocations/GetAllocationsRequest.php @@ -2,7 +2,6 @@ namespace Pterodactyl\Http\Requests\Api\Application\Allocations; -use Pterodactyl\Models\Node; use Pterodactyl\Services\Acl\Api\AdminAcl; use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest; @@ -17,15 +16,4 @@ class GetAllocationsRequest extends ApplicationApiRequest * @var int */ protected $permission = AdminAcl::READ; - - /** - * Determine if the node that we are requesting the allocations - * for exists on the Panel. - */ - public function resourceExists(): bool - { - $node = $this->route()->parameter('node'); - - return $node instanceof Node && $node->exists; - } } diff --git a/app/Http/Requests/Api/Application/ApplicationApiRequest.php b/app/Http/Requests/Api/Application/ApplicationApiRequest.php index 064a368533..5dc903f533 100644 --- a/app/Http/Requests/Api/Application/ApplicationApiRequest.php +++ b/app/Http/Requests/Api/Application/ApplicationApiRequest.php @@ -2,14 +2,12 @@ namespace Pterodactyl\Http\Requests\Api\Application; -use Pterodactyl\Models\ApiKey; +use Webmozart\Assert\Assert; use Illuminate\Validation\Validator; +use Illuminate\Database\Eloquent\Model; use Pterodactyl\Services\Acl\Api\AdminAcl; use Illuminate\Foundation\Http\FormRequest; use Pterodactyl\Exceptions\PterodactylException; -use Pterodactyl\Http\Middleware\Api\ApiSubstituteBindings; -use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; -use Symfony\Component\Routing\Exception\InvalidParameterException; abstract class ApplicationApiRequest extends FormRequest { @@ -49,15 +47,7 @@ public function authorize(): bool throw new PterodactylException('An ACL resource must be defined on API requests.'); } - return AdminAcl::check($this->key(), $this->resource, $this->permission); - } - - /** - * Determine if the requested resource exists on the server. - */ - public function resourceExists(): bool - { - return true; + return AdminAcl::check($this->attributes->get('api_key'), $this->resource, $this->permission); } /** @@ -68,35 +58,6 @@ public function rules(): array return []; } - /** - * Return the API key being used for the request. - */ - public function key(): ApiKey - { - return $this->attributes->get('api_key'); - } - - /** - * Grab a model from the route parameters. If no model is found in the - * binding mappings an exception will be thrown. - * - * @return mixed - * - * @deprecated - * - * @throws \Symfony\Component\Routing\Exception\InvalidParameterException - */ - public function getModel(string $model) - { - $parameterKey = array_get(array_flip(ApiSubstituteBindings::getMappings()), $model); - - if (is_null($parameterKey)) { - throw new InvalidParameterException(); - } - - return $this->route()->parameter($parameterKey); - } - /** * Helper method allowing a developer to easily hook into this logic without having * to remember what the method name is called or where to use it. By default this is @@ -108,50 +69,26 @@ public function withValidator(Validator $validator): void } /** - * Validate that the resource exists and can be accessed prior to booting - * the validator and attempting to use the data. + * Returns the named route parameter and asserts that it is a real model that + * exists in the database. * - * @throws \Illuminate\Auth\Access\AuthorizationException - */ - protected function prepareForValidation() - { - if (!$this->passesAuthorization()) { - $this->failedAuthorization(); - } - - $this->hasValidated = true; - } - - /* - * Determine if the request passes the authorization check as well - * as the exists check. + * @template T of \Illuminate\Database\Eloquent\Model * - * @return bool + * @param class-string $expect * - * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException + * @return T + * @noinspection PhpUndefinedClassInspection + * @noinspection PhpDocSignatureInspection */ - protected function passesAuthorization() + public function parameter(string $key, string $expect) { - // If we have already validated we do not need to call this function - // again. This is needed to work around Laravel's normal auth validation - // that occurs after validating the request params since we are doing auth - // validation in the prepareForValidation() function. - if ($this->hasValidated) { - return true; - } + $value = $this->route()->parameter($key); - if (!parent::passesAuthorization()) { - return false; - } - - // Only let the user know that a resource does not exist if they are - // authenticated to access the endpoint. This avoids exposing that - // an item exists (or does not exist) to the user until they can prove - // that they have permission to know about it. - if ($this->attributes->get('is_missing_model', false) || !$this->resourceExists()) { - throw new NotFoundHttpException(trans('exceptions.api.resource_not_found')); - } + Assert::isInstanceOf($value, $expect); + Assert::isInstanceOf($value, Model::class); + Assert::true($value->exists); - return true; + /* @var T $value */ + return $value; } } diff --git a/app/Http/Requests/Api/Application/Locations/DeleteLocationRequest.php b/app/Http/Requests/Api/Application/Locations/DeleteLocationRequest.php index ac58314f9c..880a58d1aa 100644 --- a/app/Http/Requests/Api/Application/Locations/DeleteLocationRequest.php +++ b/app/Http/Requests/Api/Application/Locations/DeleteLocationRequest.php @@ -2,7 +2,6 @@ namespace Pterodactyl\Http\Requests\Api\Application\Locations; -use Pterodactyl\Models\Location; use Pterodactyl\Services\Acl\Api\AdminAcl; use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest; @@ -17,14 +16,4 @@ class DeleteLocationRequest extends ApplicationApiRequest * @var int */ protected $permission = AdminAcl::WRITE; - - /** - * Determine if the requested location exists on the Panel. - */ - public function resourceExists(): bool - { - $location = $this->route()->parameter('location'); - - return $location instanceof Location && $location->exists; - } } diff --git a/app/Http/Requests/Api/Application/Locations/GetLocationRequest.php b/app/Http/Requests/Api/Application/Locations/GetLocationRequest.php index f7c10e0c00..dea82db335 100644 --- a/app/Http/Requests/Api/Application/Locations/GetLocationRequest.php +++ b/app/Http/Requests/Api/Application/Locations/GetLocationRequest.php @@ -2,17 +2,6 @@ namespace Pterodactyl\Http\Requests\Api\Application\Locations; -use Pterodactyl\Models\Location; - class GetLocationRequest extends GetLocationsRequest { - /** - * Determine if the requested location exists on the Panel. - */ - public function resourceExists(): bool - { - $location = $this->route()->parameter('location'); - - return $location instanceof Location && $location->exists; - } } diff --git a/app/Http/Requests/Api/Application/Locations/UpdateLocationRequest.php b/app/Http/Requests/Api/Application/Locations/UpdateLocationRequest.php index c775e2b1b1..ce42e6f051 100644 --- a/app/Http/Requests/Api/Application/Locations/UpdateLocationRequest.php +++ b/app/Http/Requests/Api/Application/Locations/UpdateLocationRequest.php @@ -6,16 +6,6 @@ class UpdateLocationRequest extends StoreLocationRequest { - /** - * Determine if the requested location exists on the Panel. - */ - public function resourceExists(): bool - { - $location = $this->route()->parameter('location'); - - return $location instanceof Location && $location->exists; - } - /** * Rules to validate this request against. */ diff --git a/app/Http/Requests/Api/Application/Nests/Eggs/GetEggRequest.php b/app/Http/Requests/Api/Application/Nests/Eggs/GetEggRequest.php index 80304ab279..e2ae0fc803 100644 --- a/app/Http/Requests/Api/Application/Nests/Eggs/GetEggRequest.php +++ b/app/Http/Requests/Api/Application/Nests/Eggs/GetEggRequest.php @@ -2,8 +2,6 @@ namespace Pterodactyl\Http\Requests\Api\Application\Nests\Eggs; -use Pterodactyl\Models\Egg; -use Pterodactyl\Models\Nest; use Pterodactyl\Services\Acl\Api\AdminAcl; use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest; @@ -18,12 +16,4 @@ class GetEggRequest extends ApplicationApiRequest * @var int */ protected $permission = AdminAcl::READ; - - /** - * Determine if the requested egg exists for the selected nest. - */ - public function resourceExists(): bool - { - return $this->getModel(Nest::class)->id === $this->getModel(Egg::class)->nest_id; - } } diff --git a/app/Http/Requests/Api/Application/Nodes/DeleteNodeRequest.php b/app/Http/Requests/Api/Application/Nodes/DeleteNodeRequest.php index eb1d1d9a52..5b9bebf270 100644 --- a/app/Http/Requests/Api/Application/Nodes/DeleteNodeRequest.php +++ b/app/Http/Requests/Api/Application/Nodes/DeleteNodeRequest.php @@ -2,7 +2,6 @@ namespace Pterodactyl\Http\Requests\Api\Application\Nodes; -use Pterodactyl\Models\Node; use Pterodactyl\Services\Acl\Api\AdminAcl; use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest; @@ -17,15 +16,4 @@ class DeleteNodeRequest extends ApplicationApiRequest * @var int */ protected $permission = AdminAcl::WRITE; - - /** - * Determine if the node being requested for editing exists - * on the Panel before validating the data. - */ - public function resourceExists(): bool - { - $node = $this->route()->parameter('node'); - - return $node instanceof Node && $node->exists; - } } diff --git a/app/Http/Requests/Api/Application/Nodes/GetNodeRequest.php b/app/Http/Requests/Api/Application/Nodes/GetNodeRequest.php index bbb1570352..6d231bc97d 100644 --- a/app/Http/Requests/Api/Application/Nodes/GetNodeRequest.php +++ b/app/Http/Requests/Api/Application/Nodes/GetNodeRequest.php @@ -2,17 +2,6 @@ namespace Pterodactyl\Http\Requests\Api\Application\Nodes; -use Pterodactyl\Models\Node; - class GetNodeRequest extends GetNodesRequest { - /** - * Determine if the requested node exists on the Panel. - */ - public function resourceExists(): bool - { - $node = $this->route()->parameter('node'); - - return $node instanceof Node && $node->exists; - } } diff --git a/app/Http/Requests/Api/Application/Nodes/UpdateNodeRequest.php b/app/Http/Requests/Api/Application/Nodes/UpdateNodeRequest.php index 2da2d40611..7133bd0b52 100644 --- a/app/Http/Requests/Api/Application/Nodes/UpdateNodeRequest.php +++ b/app/Http/Requests/Api/Application/Nodes/UpdateNodeRequest.php @@ -12,8 +12,8 @@ class UpdateNodeRequest extends StoreNodeRequest */ public function rules(array $rules = null): array { - $nodeId = $this->getModel(Node::class)->id; + $node = $this->route()->parameter('node')->id; - return parent::rules(Node::getRulesForUpdate($nodeId)); + return parent::rules(Node::getRulesForUpdate($node)); } } diff --git a/app/Http/Requests/Api/Application/Servers/Databases/GetServerDatabaseRequest.php b/app/Http/Requests/Api/Application/Servers/Databases/GetServerDatabaseRequest.php index 2dff1374e4..7761624403 100644 --- a/app/Http/Requests/Api/Application/Servers/Databases/GetServerDatabaseRequest.php +++ b/app/Http/Requests/Api/Application/Servers/Databases/GetServerDatabaseRequest.php @@ -16,15 +16,4 @@ class GetServerDatabaseRequest extends ApplicationApiRequest * @var int */ protected $permission = AdminAcl::READ; - - /** - * Determine if the requested server database exists. - */ - public function resourceExists(): bool - { - $server = $this->route()->parameter('server'); - $database = $this->route()->parameter('database'); - - return $database->server_id === $server->id; - } } diff --git a/app/Http/Requests/Api/Application/Servers/GetExternalServerRequest.php b/app/Http/Requests/Api/Application/Servers/GetExternalServerRequest.php index 902bc60c51..39ec449d28 100644 --- a/app/Http/Requests/Api/Application/Servers/GetExternalServerRequest.php +++ b/app/Http/Requests/Api/Application/Servers/GetExternalServerRequest.php @@ -2,19 +2,11 @@ namespace Pterodactyl\Http\Requests\Api\Application\Servers; -use Pterodactyl\Models\Server; use Pterodactyl\Services\Acl\Api\AdminAcl; -use Pterodactyl\Exceptions\Repository\RecordNotFoundException; -use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest; class GetExternalServerRequest extends ApplicationApiRequest { - /** - * @var \Pterodactyl\Models\Server - */ - private $serverModel; - /** * @var string */ @@ -24,30 +16,4 @@ class GetExternalServerRequest extends ApplicationApiRequest * @var int */ protected $permission = AdminAcl::READ; - - /** - * Determine if the requested external user exists. - */ - public function resourceExists(): bool - { - $repository = $this->container->make(ServerRepositoryInterface::class); - - try { - $this->serverModel = $repository->findFirstWhere([ - ['external_id', '=', $this->route()->parameter('external_id')], - ]); - } catch (RecordNotFoundException $exception) { - return false; - } - - return true; - } - - /** - * Return the server model for the requested external server. - */ - public function getServerModel(): Server - { - return $this->serverModel; - } } diff --git a/app/Http/Requests/Api/Application/Servers/UpdateServerBuildConfigurationRequest.php b/app/Http/Requests/Api/Application/Servers/UpdateServerBuildConfigurationRequest.php index 3f534b08a7..f043f82b42 100644 --- a/app/Http/Requests/Api/Application/Servers/UpdateServerBuildConfigurationRequest.php +++ b/app/Http/Requests/Api/Application/Servers/UpdateServerBuildConfigurationRequest.php @@ -12,7 +12,7 @@ class UpdateServerBuildConfigurationRequest extends ServerWriteRequest */ public function rules(): array { - $rules = Server::getRulesForUpdate($this->getModel(Server::class)); + $rules = Server::getRulesForUpdate($this->parameter('server', Server::class)); return [ 'allocation' => $rules['allocation_id'], diff --git a/app/Http/Requests/Api/Application/Servers/UpdateServerDetailsRequest.php b/app/Http/Requests/Api/Application/Servers/UpdateServerDetailsRequest.php index 06bd22f5fd..3540b88cf2 100644 --- a/app/Http/Requests/Api/Application/Servers/UpdateServerDetailsRequest.php +++ b/app/Http/Requests/Api/Application/Servers/UpdateServerDetailsRequest.php @@ -11,7 +11,7 @@ class UpdateServerDetailsRequest extends ServerWriteRequest */ public function rules(): array { - $rules = Server::getRulesForUpdate($this->getModel(Server::class)); + $rules = Server::getRulesForUpdate($this->parameter('server', Server::class)); return [ 'external_id' => $rules['external_id'], diff --git a/app/Http/Requests/Api/Application/Servers/UpdateServerStartupRequest.php b/app/Http/Requests/Api/Application/Servers/UpdateServerStartupRequest.php index a873e01980..c9b3c6ad09 100644 --- a/app/Http/Requests/Api/Application/Servers/UpdateServerStartupRequest.php +++ b/app/Http/Requests/Api/Application/Servers/UpdateServerStartupRequest.php @@ -23,7 +23,7 @@ class UpdateServerStartupRequest extends ApplicationApiRequest */ public function rules(): array { - $data = Server::getRulesForUpdate($this->getModel(Server::class)); + $data = Server::getRulesForUpdate($this->parameter('server', Server::class)); return [ 'startup' => $data['startup'], diff --git a/app/Http/Requests/Api/Application/Users/DeleteUserRequest.php b/app/Http/Requests/Api/Application/Users/DeleteUserRequest.php index 56c028a6e6..c7592e693c 100644 --- a/app/Http/Requests/Api/Application/Users/DeleteUserRequest.php +++ b/app/Http/Requests/Api/Application/Users/DeleteUserRequest.php @@ -2,7 +2,6 @@ namespace Pterodactyl\Http\Requests\Api\Application\Users; -use Pterodactyl\Models\User; use Pterodactyl\Services\Acl\Api\AdminAcl; use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest; @@ -17,14 +16,4 @@ class DeleteUserRequest extends ApplicationApiRequest * @var int */ protected $permission = AdminAcl::WRITE; - - /** - * Determine if the requested user exists on the Panel. - */ - public function resourceExists(): bool - { - $user = $this->route()->parameter('user'); - - return $user instanceof User && $user->exists; - } } diff --git a/app/Http/Requests/Api/Application/Users/GetExternalUserRequest.php b/app/Http/Requests/Api/Application/Users/GetExternalUserRequest.php index 5f63d04ba6..a3bd7c9cfd 100644 --- a/app/Http/Requests/Api/Application/Users/GetExternalUserRequest.php +++ b/app/Http/Requests/Api/Application/Users/GetExternalUserRequest.php @@ -2,19 +2,11 @@ namespace Pterodactyl\Http\Requests\Api\Application\Users; -use Pterodactyl\Models\User; use Pterodactyl\Services\Acl\Api\AdminAcl; -use Pterodactyl\Contracts\Repository\UserRepositoryInterface; -use Pterodactyl\Exceptions\Repository\RecordNotFoundException; use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest; class GetExternalUserRequest extends ApplicationApiRequest { - /** - * @var User - */ - private $userModel; - /** * @var string */ @@ -24,30 +16,4 @@ class GetExternalUserRequest extends ApplicationApiRequest * @var int */ protected $permission = AdminAcl::READ; - - /** - * Determine if the requested external user exists. - */ - public function resourceExists(): bool - { - $repository = $this->container->make(UserRepositoryInterface::class); - - try { - $this->userModel = $repository->findFirstWhere([ - ['external_id', '=', $this->route()->parameter('external_id')], - ]); - } catch (RecordNotFoundException $exception) { - return false; - } - - return true; - } - - /** - * Return the user model for the requested external user. - */ - public function getUserModel(): User - { - return $this->userModel; - } } diff --git a/app/Http/Requests/Api/Application/Users/UpdateUserRequest.php b/app/Http/Requests/Api/Application/Users/UpdateUserRequest.php index b341eaa241..fa2e1291c7 100644 --- a/app/Http/Requests/Api/Application/Users/UpdateUserRequest.php +++ b/app/Http/Requests/Api/Application/Users/UpdateUserRequest.php @@ -11,7 +11,7 @@ class UpdateUserRequest extends StoreUserRequest */ public function rules(array $rules = null): array { - $userId = $this->getModel(User::class)->id; + $userId = $this->parameter('user', User::class)->id; return parent::rules(User::getRulesForUpdate($userId)); } diff --git a/app/Http/Requests/Api/Client/Servers/Databases/DeleteDatabaseRequest.php b/app/Http/Requests/Api/Client/Servers/Databases/DeleteDatabaseRequest.php index 92b14157a5..eb2cbc57e5 100644 --- a/app/Http/Requests/Api/Client/Servers/Databases/DeleteDatabaseRequest.php +++ b/app/Http/Requests/Api/Client/Servers/Databases/DeleteDatabaseRequest.php @@ -2,8 +2,6 @@ namespace Pterodactyl\Http\Requests\Api\Client\Servers\Databases; -use Pterodactyl\Models\Server; -use Pterodactyl\Models\Database; use Pterodactyl\Models\Permission; use Pterodactyl\Contracts\Http\ClientPermissionsRequest; use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest; @@ -14,9 +12,4 @@ public function permission(): string { return Permission::ACTION_DATABASE_DELETE; } - - public function resourceExists(): bool - { - return $this->getModel(Server::class)->id === $this->getModel(Database::class)->server_id; - } } diff --git a/app/Http/Requests/Api/Client/Servers/Files/DownloadFileRequest.php b/app/Http/Requests/Api/Client/Servers/Files/DownloadFileRequest.php index 0386b8555d..c588c9b23f 100644 --- a/app/Http/Requests/Api/Client/Servers/Files/DownloadFileRequest.php +++ b/app/Http/Requests/Api/Client/Servers/Files/DownloadFileRequest.php @@ -13,6 +13,6 @@ class DownloadFileRequest extends ClientApiRequest */ public function authorize(): bool { - return $this->user()->can('file.read', $this->getModel(Server::class)); + return $this->user()->can('file.read', $this->parameter('server', Server::class)); } } diff --git a/app/Models/Allocation.php b/app/Models/Allocation.php index 49673921b1..47b560e487 100644 --- a/app/Models/Allocation.php +++ b/app/Models/Allocation.php @@ -80,6 +80,14 @@ class Allocation extends Model 'notes' => 'nullable|string|max:256', ]; + /** + * {@inheritDoc} + */ + public function getRouteKeyName(): string + { + return $this->getKeyName(); + } + /** * Return a hashid encoded string to represent the ID of the allocation. * diff --git a/app/Models/Database.php b/app/Models/Database.php index ebe8867933..e54d9f5ce2 100644 --- a/app/Models/Database.php +++ b/app/Models/Database.php @@ -2,6 +2,9 @@ namespace Pterodactyl\Models; +use Illuminate\Container\Container; +use Pterodactyl\Contracts\Extensions\HashidsInterface; + /** * @property int $id * @property int $server_id @@ -71,6 +74,36 @@ class Database extends Model 'password' => 'string', ]; + /** + * {@inheritDoc} + */ + public function getRouteKeyName(): string + { + return $this->getKeyName(); + } + + /** + * Resolves the database using the ID by checking if the value provided is a HashID + * string value, or just the ID to the database itself. + * + * @param mixed $value + * @param string|null $field + * + * @return \Illuminate\Database\Eloquent\Model|null + * + * @throws \Illuminate\Contracts\Container\BindingResolutionException + */ + public function resolveRouteBinding($value, $field = null) + { + if (is_scalar($value) && ($field ?? $this->getRouteKeyName()) === 'id') { + $value = ctype_digit((string) $value) + ? $value + : Container::getInstance()->make(HashidsInterface::class)->decodeFirst($value); + } + + return $this->where($field ?? $this->getRouteKeyName(), $value)->firstOrFail(); + } + /** * Gets the host database server associated with a database. * diff --git a/app/Models/Location.php b/app/Models/Location.php index bc9a1fb4fd..c490e56caf 100644 --- a/app/Models/Location.php +++ b/app/Models/Location.php @@ -43,6 +43,14 @@ class Location extends Model 'long' => 'string|nullable|between:1,191', ]; + /** + * {@inheritDoc} + */ + public function getRouteKeyName(): string + { + return $this->getKeyName(); + } + /** * Gets the nodes in a specified location. * diff --git a/app/Models/Model.php b/app/Models/Model.php index 3fca705fd2..23cf5e7e97 100644 --- a/app/Models/Model.php +++ b/app/Models/Model.php @@ -68,6 +68,20 @@ protected static function boot() }); } + /** + * Returns the model key to use for route model binding. By default we'll + * assume every model uses a UUID field for this. If the model does not have + * a UUID and is using a different key it should be specified on the model + * itself. + * + * You may also optionally override this on a per-route basis by declaring + * the key name in the URL definition, like "{user:id}". + */ + public function getRouteKeyName(): string + { + return 'uuid'; + } + /** * Set the model to skip validation when saving. * diff --git a/app/Models/Schedule.php b/app/Models/Schedule.php index 82a43c732c..81c30768c0 100644 --- a/app/Models/Schedule.php +++ b/app/Models/Schedule.php @@ -123,6 +123,14 @@ class Schedule extends Model 'next_run_at' => 'nullable|date', ]; + /** + * {@inheritDoc} + */ + public function getRouteKeyName(): string + { + return $this->getKeyName(); + } + /** * Returns the schedule's execution crontab entry as a string. * diff --git a/app/Models/Server.php b/app/Models/Server.php index bd539282a2..b713158323 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -9,6 +9,8 @@ use Pterodactyl\Exceptions\Http\Server\ServerStateConflictException; /** + * Pterodactyl\Models\Server. + * * @property int $id * @property string|null $external_id * @property string $uuid @@ -24,33 +26,75 @@ * @property int $disk * @property int $io * @property int $cpu - * @property string $threads + * @property string|null $threads * @property bool $oom_disabled * @property int $allocation_id * @property int $nest_id * @property int $egg_id * @property string $startup * @property string $image - * @property int $allocation_limit - * @property int $database_limit + * @property int|null $allocation_limit + * @property int|null $database_limit * @property int $backup_limit - * @property \Carbon\Carbon $created_at - * @property \Carbon\Carbon $updated_at - * @property \Pterodactyl\Models\User $user - * @property \Pterodactyl\Models\Subuser[]|\Illuminate\Database\Eloquent\Collection $subusers - * @property \Pterodactyl\Models\Allocation $allocation - * @property \Pterodactyl\Models\Allocation[]|\Illuminate\Database\Eloquent\Collection $allocations - * @property \Pterodactyl\Models\Node $node + * @property \Illuminate\Support\Carbon|null $created_at + * @property \Illuminate\Support\Carbon|null $updated_at + * @property \Pterodactyl\Models\Allocation|null $allocation + * @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\Allocation[] $allocations + * @property int|null $allocations_count + * @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\AuditLog[] $audits + * @property int|null $audits_count + * @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\Backup[] $backups + * @property int|null $backups_count + * @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\Database[] $databases + * @property int|null $databases_count + * @property \Pterodactyl\Models\Egg|null $egg + * @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\Mount[] $mounts + * @property int|null $mounts_count * @property \Pterodactyl\Models\Nest $nest - * @property \Pterodactyl\Models\Egg $egg - * @property \Pterodactyl\Models\EggVariable[]|\Illuminate\Database\Eloquent\Collection $variables - * @property \Pterodactyl\Models\Schedule[]|\Illuminate\Database\Eloquent\Collection $schedule - * @property \Pterodactyl\Models\Database[]|\Illuminate\Database\Eloquent\Collection $databases - * @property \Pterodactyl\Models\Location $location - * @property \Pterodactyl\Models\ServerTransfer $transfer - * @property \Pterodactyl\Models\Backup[]|\Illuminate\Database\Eloquent\Collection $backups - * @property \Pterodactyl\Models\Mount[]|\Illuminate\Database\Eloquent\Collection $mounts - * @property \Pterodactyl\Models\AuditLog[] $audits + * @property \Pterodactyl\Models\Node $node + * @property \Illuminate\Notifications\DatabaseNotificationCollection|\Illuminate\Notifications\DatabaseNotification[] $notifications + * @property int|null $notifications_count + * @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\Schedule[] $schedules + * @property int|null $schedules_count + * @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\Subuser[] $subusers + * @property int|null $subusers_count + * @property \Pterodactyl\Models\ServerTransfer|null $transfer + * @property \Pterodactyl\Models\User $user + * @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\EggVariable[] $variables + * @property int|null $variables_count + * + * @method static \Database\Factories\ServerFactory factory(...$parameters) + * @method static \Illuminate\Database\Eloquent\Builder|Server newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|Server newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|Server query() + * @method static \Illuminate\Database\Eloquent\Builder|Server whereAllocationId($value) + * @method static \Illuminate\Database\Eloquent\Builder|Server whereAllocationLimit($value) + * @method static \Illuminate\Database\Eloquent\Builder|Server whereBackupLimit($value) + * @method static \Illuminate\Database\Eloquent\Builder|Server whereCpu($value) + * @method static \Illuminate\Database\Eloquent\Builder|Server whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|Server whereDatabaseLimit($value) + * @method static \Illuminate\Database\Eloquent\Builder|Server whereDescription($value) + * @method static \Illuminate\Database\Eloquent\Builder|Server whereDisk($value) + * @method static \Illuminate\Database\Eloquent\Builder|Server whereEggId($value) + * @method static \Illuminate\Database\Eloquent\Builder|Server whereExternalId($value) + * @method static \Illuminate\Database\Eloquent\Builder|Server whereId($value) + * @method static \Illuminate\Database\Eloquent\Builder|Server whereImage($value) + * @method static \Illuminate\Database\Eloquent\Builder|Server whereIo($value) + * @method static \Illuminate\Database\Eloquent\Builder|Server whereMemory($value) + * @method static \Illuminate\Database\Eloquent\Builder|Server whereName($value) + * @method static \Illuminate\Database\Eloquent\Builder|Server whereNestId($value) + * @method static \Illuminate\Database\Eloquent\Builder|Server whereNodeId($value) + * @method static \Illuminate\Database\Eloquent\Builder|Server whereOomDisabled($value) + * @method static \Illuminate\Database\Eloquent\Builder|Server whereOwnerId($value) + * @method static \Illuminate\Database\Eloquent\Builder|Server whereSkipScripts($value) + * @method static \Illuminate\Database\Eloquent\Builder|Server whereStartup($value) + * @method static \Illuminate\Database\Eloquent\Builder|Server whereStatus($value) + * @method static \Illuminate\Database\Eloquent\Builder|Server whereSwap($value) + * @method static \Illuminate\Database\Eloquent\Builder|Server whereThreads($value) + * @method static \Illuminate\Database\Eloquent\Builder|Server whereUpdatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|Server whereUuid($value) + * @method static \Illuminate\Database\Eloquent\Builder|Server whereUuidShort($value) + * @mixin \Eloquent */ class Server extends Model { @@ -273,7 +317,7 @@ public function node() * * @return \Illuminate\Database\Eloquent\Relations\HasMany */ - public function schedule() + public function schedules() { return $this->hasMany(Schedule::class); } diff --git a/app/Models/Task.php b/app/Models/Task.php index 82ba72370f..b02503286a 100644 --- a/app/Models/Task.php +++ b/app/Models/Task.php @@ -105,6 +105,14 @@ class Task extends Model 'continue_on_failure' => 'boolean', ]; + /** + * {@inheritDoc} + */ + public function getRouteKeyName(): string + { + return $this->getKeyName(); + } + /** * Return a hashid encoded string to represent the ID of the task. * diff --git a/app/Models/User.php b/app/Models/User.php index 42ddc774ec..196ad18ede 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -201,7 +201,7 @@ public static function getRules() */ public function toVueObject(): array { - return (new Collection($this->toArray()))->except(['id', 'external_id'])->toArray(); + return Collection::make($this->toArray())->except(['id', 'external_id'])->toArray(); } /** diff --git a/app/Models/UserSSHKey.php b/app/Models/UserSSHKey.php index 718c6d1cfd..658bac6c0f 100644 --- a/app/Models/UserSSHKey.php +++ b/app/Models/UserSSHKey.php @@ -17,6 +17,7 @@ * @property \Illuminate\Support\Carbon|null $updated_at * @property \Illuminate\Support\Carbon|null $deleted_at * @property \Pterodactyl\Models\User $user + * * @method static \Illuminate\Database\Eloquent\Builder|UserSSHKey newModelQuery() * @method static \Illuminate\Database\Eloquent\Builder|UserSSHKey newQuery() * @method static \Illuminate\Database\Query\Builder|UserSSHKey onlyTrashed() @@ -32,6 +33,7 @@ * @method static \Illuminate\Database\Query\Builder|UserSSHKey withTrashed() * @method static \Illuminate\Database\Query\Builder|UserSSHKey withoutTrashed() * @mixin \Eloquent + * * @method static \Database\Factories\UserSSHKeyFactory factory(...$parameters) */ class UserSSHKey extends Model diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index 188509fce0..428b9512a5 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -3,6 +3,7 @@ namespace Pterodactyl\Providers; use Illuminate\Http\Request; +use Pterodactyl\Models\Database; use Illuminate\Support\Facades\Route; use Illuminate\Cache\RateLimiting\Limit; use Illuminate\Support\Facades\RateLimiter; @@ -26,6 +27,11 @@ public function boot() return preg_match(self::FILE_PATH_REGEX, $request->getPathInfo()) === 1; }); + // This is needed to make use of the "resolveRouteBinding" functionality in the + // model. Without it you'll never trigger that logic flow thus resulting in a 404 + // error because we request databases with a HashID, and not with a normal ID. + Route::model('database', Database::class); + $this->routes(function () { Route::middleware(['web', 'csrf'])->group(function () { Route::middleware('auth')->group(base_path('routes/base.php')); @@ -36,14 +42,18 @@ public function boot() Route::middleware('api')->group(function () { Route::middleware(['application-api', 'throttle:api.application']) ->prefix('/api/application') + ->scopeBindings() ->group(base_path('routes/api-application.php')); Route::middleware(['client-api', 'throttle:api.client']) ->prefix('/api/client') + ->scopeBindings() ->group(base_path('routes/api-client.php')); }); - Route::middleware('daemon')->prefix('/api/remote') + Route::middleware('daemon') + ->prefix('/api/remote') + ->scopeBindings() ->group(base_path('routes/api-remote.php')); }); } diff --git a/routes/api-application.php b/routes/api-application.php index 29d3d8aeb0..dc6b0e5bb6 100644 --- a/routes/api-application.php +++ b/routes/api-application.php @@ -14,13 +14,13 @@ Route::group(['prefix' => '/users'], function () { Route::get('/', [Application\Users\UserController::class, 'index'])->name('api.application.users'); - Route::get('/{user}', [Application\Users\UserController::class, 'view'])->name('api.application.users.view'); + Route::get('/{user:id}', [Application\Users\UserController::class, 'view'])->name('api.application.users.view'); Route::get('/external/{external_id}', [Application\Users\ExternalUserController::class, 'index'])->name('api.application.users.external'); Route::post('/', [Application\Users\UserController::class, 'store']); - Route::patch('/{user}', [Application\Users\UserController::class, 'update']); + Route::patch('/{user:id}', [Application\Users\UserController::class, 'update']); - Route::delete('/{user}', [Application\Users\UserController::class, 'delete']); + Route::delete('/{user:id}', [Application\Users\UserController::class, 'delete']); }); /* @@ -34,18 +34,18 @@ Route::group(['prefix' => '/nodes'], function () { Route::get('/', [Application\Nodes\NodeController::class, 'index'])->name('api.application.nodes'); Route::get('/deployable', Application\Nodes\NodeDeploymentController::class); - Route::get('/{node}', [Application\Nodes\NodeController::class, 'view'])->name('api.application.nodes.view'); - Route::get('/{node}/configuration', Application\Nodes\NodeConfigurationController::class); + Route::get('/{node:id}', [Application\Nodes\NodeController::class, 'view'])->name('api.application.nodes.view'); + Route::get('/{node:id}/configuration', Application\Nodes\NodeConfigurationController::class); Route::post('/', [Application\Nodes\NodeController::class, 'store']); - Route::patch('/{node}', [Application\Nodes\NodeController::class, 'update']); + Route::patch('/{node:id}', [Application\Nodes\NodeController::class, 'update']); - Route::delete('/{node}', [Application\Nodes\NodeController::class, 'delete']); + Route::delete('/{node:id}', [Application\Nodes\NodeController::class, 'delete']); - Route::group(['prefix' => '/{node}/allocations'], function () { + Route::group(['prefix' => '/{node:id}/allocations'], function () { Route::get('/', [Application\Nodes\AllocationController::class, 'index'])->name('api.application.allocations'); Route::post('/', [Application\Nodes\AllocationController::class, 'store']); - Route::delete('/{allocation}', [Application\Nodes\AllocationController::class, 'delete'])->name('api.application.allocations.view'); + Route::delete('/{allocation:id}', [Application\Nodes\AllocationController::class, 'delete'])->name('api.application.allocations.view'); }); }); @@ -59,12 +59,12 @@ */ Route::group(['prefix' => '/locations'], function () { Route::get('/', [Application\Locations\LocationController::class, 'index'])->name('api.applications.locations'); - Route::get('/{location}', [Application\Locations\LocationController::class, 'view'])->name('api.application.locations.view'); + Route::get('/{location:id}', [Application\Locations\LocationController::class, 'view'])->name('api.application.locations.view'); Route::post('/', [Application\Locations\LocationController::class, 'store']); - Route::patch('/{location}', [Application\Locations\LocationController::class, 'update']); + Route::patch('/{location:id}', [Application\Locations\LocationController::class, 'update']); - Route::delete('/{location}', [Application\Locations\LocationController::class, 'delete']); + Route::delete('/{location:id}', [Application\Locations\LocationController::class, 'delete']); }); /* @@ -77,30 +77,30 @@ */ Route::group(['prefix' => '/servers'], function () { Route::get('/', [Application\Servers\ServerController::class, 'index'])->name('api.application.servers'); - Route::get('/{server}', [Application\Servers\ServerController::class, 'view'])->name('api.application.servers.view'); + Route::get('/{server:id}', [Application\Servers\ServerController::class, 'view'])->name('api.application.servers.view'); Route::get('/external/{external_id}', [Application\Servers\ExternalServerController::class, 'index'])->name('api.application.servers.external'); - Route::patch('/{server}/details', [Application\Servers\ServerDetailsController::class, 'details'])->name('api.application.servers.details'); - Route::patch('/{server}/build', [Application\Servers\ServerDetailsController::class, 'build'])->name('api.application.servers.build'); - Route::patch('/{server}/startup', [Application\Servers\StartupController::class, 'index'])->name('api.application.servers.startup'); + Route::patch('/{server:id}/details', [Application\Servers\ServerDetailsController::class, 'details'])->name('api.application.servers.details'); + Route::patch('/{server:id}/build', [Application\Servers\ServerDetailsController::class, 'build'])->name('api.application.servers.build'); + Route::patch('/{server:id}/startup', [Application\Servers\StartupController::class, 'index'])->name('api.application.servers.startup'); Route::post('/', [Application\Servers\ServerController::class, 'store']); - Route::post('/{server}/suspend', [Application\Servers\ServerManagementController::class, 'suspend'])->name('api.application.servers.suspend'); - Route::post('/{server}/unsuspend', [Application\Servers\ServerManagementController::class, 'unsuspend'])->name('api.application.servers.unsuspend'); - Route::post('/{server}/reinstall', [Application\Servers\ServerManagementController::class, 'reinstall'])->name('api.application.servers.reinstall'); + Route::post('/{server:id}/suspend', [Application\Servers\ServerManagementController::class, 'suspend'])->name('api.application.servers.suspend'); + Route::post('/{server:id}/unsuspend', [Application\Servers\ServerManagementController::class, 'unsuspend'])->name('api.application.servers.unsuspend'); + Route::post('/{server:id}/reinstall', [Application\Servers\ServerManagementController::class, 'reinstall'])->name('api.application.servers.reinstall'); - Route::delete('/{server}', [Application\Servers\ServerController::class, 'delete']); - Route::delete('/{server}/{force?}', [Application\Servers\ServerController::class, 'delete']); + Route::delete('/{server:id}', [Application\Servers\ServerController::class, 'delete']); + Route::delete('/{server:id}/{force?}', [Application\Servers\ServerController::class, 'delete']); // Database Management Endpoint - Route::group(['prefix' => '/{server}/databases'], function () { + Route::group(['prefix' => '/{server:id}/databases'], function () { Route::get('/', [Application\Servers\DatabaseController::class, 'index'])->name('api.application.servers.databases'); - Route::get('/{database}', [Application\Servers\DatabaseController::class, 'view'])->name('api.application.servers.databases.view'); + Route::get('/{database:id}', [Application\Servers\DatabaseController::class, 'view'])->name('api.application.servers.databases.view'); Route::post('/', [Application\Servers\DatabaseController::class, 'store']); - Route::post('/{database}/reset-password', [Application\Servers\DatabaseController::class, 'resetPassword']); + Route::post('/{database:id}/reset-password', [Application\Servers\DatabaseController::class, 'resetPassword']); - Route::delete('/{database}', [Application\Servers\DatabaseController::class, 'delete']); + Route::delete('/{database:id}', [Application\Servers\DatabaseController::class, 'delete']); }); }); @@ -114,11 +114,11 @@ */ Route::group(['prefix' => '/nests'], function () { Route::get('/', [Application\Nests\NestController::class, 'index'])->name('api.application.nests'); - Route::get('/{nest}', [Application\Nests\NestController::class, 'view'])->name('api.application.nests.view'); + Route::get('/{nest:id}', [Application\Nests\NestController::class, 'view'])->name('api.application.nests.view'); // Egg Management Endpoint - Route::group(['prefix' => '/{nest}/eggs'], function () { + Route::group(['prefix' => '/{nest:id}/eggs'], function () { Route::get('/', [Application\Nests\EggController::class, 'index'])->name('api.application.nests.eggs'); - Route::get('/{egg}', [Application\Nests\EggController::class, 'view'])->name('api.application.nests.eggs.view'); + Route::get('/{egg:id}', [Application\Nests\EggController::class, 'view'])->name('api.application.nests.eggs.view'); }); }); diff --git a/tests/Integration/Api/Application/Location/LocationControllerTest.php b/tests/Integration/Api/Application/Location/LocationControllerTest.php index 106df9f924..ef3ff3e637 100644 --- a/tests/Integration/Api/Application/Location/LocationControllerTest.php +++ b/tests/Integration/Api/Application/Location/LocationControllerTest.php @@ -265,16 +265,4 @@ public function testErrorReturnedIfNoPermission() $response = $this->getJson('/api/application/locations/' . $location->id); $this->assertAccessDeniedJson($response); } - - /** - * Test that a location's existence is not exposed unless an API key has permission - * to access the resource. - */ - public function testResourceIsNotExposedWithoutPermissions() - { - $this->createNewDefaultApiKey($this->getApiUser(), ['r_locations' => 0]); - - $response = $this->getJson('/api/application/locations/nil'); - $this->assertAccessDeniedJson($response); - } } diff --git a/tests/Integration/Api/Application/Nests/EggControllerTest.php b/tests/Integration/Api/Application/Nests/EggControllerTest.php index 7fc523658f..c513535f66 100644 --- a/tests/Integration/Api/Application/Nests/EggControllerTest.php +++ b/tests/Integration/Api/Application/Nests/EggControllerTest.php @@ -120,17 +120,4 @@ public function testErrorReturnedIfNoPermission() $response = $this->getJson('/api/application/nests/' . $egg->nest_id . '/eggs'); $this->assertAccessDeniedJson($response); } - - /** - * Test that a nests's existence is not exposed unless an API key has permission - * to access the resource. - */ - public function testResourceIsNotExposedWithoutPermissions() - { - $egg = Egg::query()->findOrFail(1); - $this->createNewDefaultApiKey($this->getApiUser(), ['r_eggs' => 0]); - - $response = $this->getJson('/api/application/nests/' . $egg->nest_id . '/eggs/nil'); - $this->assertAccessDeniedJson($response); - } } diff --git a/tests/Integration/Api/Application/Nests/NestControllerTest.php b/tests/Integration/Api/Application/Nests/NestControllerTest.php index 58434ec4cc..b3e1def6d8 100644 --- a/tests/Integration/Api/Application/Nests/NestControllerTest.php +++ b/tests/Integration/Api/Application/Nests/NestControllerTest.php @@ -127,17 +127,4 @@ public function testErrorReturnedIfNoPermission() $response = $this->getJson('/api/application/nests/' . $nest->id); $this->assertAccessDeniedJson($response); } - - /** - * Test that a nest's existence is not exposed unless an API key has permission - * to access the resource. - */ - public function testResourceIsNotExposedWithoutPermissions() - { - $nest = $this->repository->find(1); - $this->createNewDefaultApiKey($this->getApiUser(), ['r_nests' => 0]); - - $response = $this->getJson('/api/application/nests/' . $nest->id); - $this->assertAccessDeniedJson($response); - } } diff --git a/tests/Integration/Api/Application/Users/ExternalUserControllerTest.php b/tests/Integration/Api/Application/Users/ExternalUserControllerTest.php index 983710b16d..74e03df544 100644 --- a/tests/Integration/Api/Application/Users/ExternalUserControllerTest.php +++ b/tests/Integration/Api/Application/Users/ExternalUserControllerTest.php @@ -66,16 +66,4 @@ public function testErrorReturnedIfNoPermission() $response = $this->getJson('/api/application/users/external/' . $user->external_id); $this->assertAccessDeniedJson($response); } - - /** - * Test that a users's existence is not exposed unless an API key has permission - * to access the resource. - */ - public function testResourceIsNotExposedWithoutPermissions() - { - $this->createNewDefaultApiKey($this->getApiUser(), ['r_users' => 0]); - - $response = $this->getJson('/api/application/users/external/nil'); - $this->assertAccessDeniedJson($response); - } } diff --git a/tests/Integration/Api/Application/Users/UserControllerTest.php b/tests/Integration/Api/Application/Users/UserControllerTest.php index f084d2ed2c..44a739db25 100644 --- a/tests/Integration/Api/Application/Users/UserControllerTest.php +++ b/tests/Integration/Api/Application/Users/UserControllerTest.php @@ -201,18 +201,6 @@ public function testErrorReturnedIfNoPermission() $this->assertAccessDeniedJson($response); } - /** - * Test that a users's existence is not exposed unless an API key has permission - * to access the resource. - */ - public function testResourceIsNotExposedWithoutPermissions() - { - $this->createNewDefaultApiKey($this->getApiUser(), ['r_users' => 0]); - - $response = $this->getJson('/api/application/users/nil'); - $this->assertAccessDeniedJson($response); - } - /** * Test that a user can be created. */ diff --git a/tests/Traits/Http/IntegrationJsonRequestAssertions.php b/tests/Traits/Http/IntegrationJsonRequestAssertions.php index e26235a929..2658520eed 100644 --- a/tests/Traits/Http/IntegrationJsonRequestAssertions.php +++ b/tests/Traits/Http/IntegrationJsonRequestAssertions.php @@ -20,7 +20,7 @@ public function assertNotFoundJson(TestResponse $response) [ 'code' => 'NotFoundHttpException', 'status' => '404', - 'detail' => 'The requested resource does not exist on this server.', + 'detail' => 'The requested resource could not be found on the server.', ], ], ], true); From bd37978a98719d5d7e45595262315ae721303230 Mon Sep 17 00:00:00 2001 From: DaneEveritt Date: Sun, 22 May 2022 14:57:06 -0400 Subject: [PATCH 044/458] Initial pass at implementing Laravel Sanctum for authorization on the API --- .../Laravel/Sanctum/NewAccessToken.php | 23 ++++ app/Http/Kernel.php | 30 ++--- .../Middleware/Api/AuthenticateIPAccess.php | 12 +- app/Http/Middleware/Api/AuthenticateKey.php | 109 ------------------ .../Middleware/Api/HandleStatelessRequest.php | 35 ------ app/Http/Middleware/VerifyCsrfToken.php | 29 ----- app/Models/ApiKey.php | 101 ++++++++++++++-- app/Models/Traits/HasAccessTokens.php | 37 ++++++ app/Models/User.php | 23 ++-- app/Providers/AuthServiceProvider.php | 8 +- composer.json | 1 + composer.lock | 69 ++++++++++- config/sanctum.php | 67 +++++++++++ 13 files changed, 324 insertions(+), 220 deletions(-) create mode 100644 app/Extensions/Laravel/Sanctum/NewAccessToken.php delete mode 100644 app/Http/Middleware/Api/AuthenticateKey.php delete mode 100644 app/Http/Middleware/Api/HandleStatelessRequest.php create mode 100644 app/Models/Traits/HasAccessTokens.php create mode 100644 config/sanctum.php diff --git a/app/Extensions/Laravel/Sanctum/NewAccessToken.php b/app/Extensions/Laravel/Sanctum/NewAccessToken.php new file mode 100644 index 0000000000..256e57b61f --- /dev/null +++ b/app/Extensions/Laravel/Sanctum/NewAccessToken.php @@ -0,0 +1,23 @@ +accessToken = $accessToken; + $this->plainTextToken = $plainTextToken; + } +} diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 3e9f1dd197..fe924119fa 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -2,7 +2,6 @@ namespace Pterodactyl\Http; -use Pterodactyl\Models\ApiKey; use Illuminate\Auth\Middleware\Authorize; use Illuminate\Auth\Middleware\Authenticate; use Illuminate\Http\Middleware\TrustProxies; @@ -16,7 +15,6 @@ use Illuminate\Routing\Middleware\ThrottleRequests; use Pterodactyl\Http\Middleware\LanguageMiddleware; use Illuminate\Foundation\Http\Kernel as HttpKernel; -use Pterodactyl\Http\Middleware\Api\AuthenticateKey; use Illuminate\Routing\Middleware\SubstituteBindings; use Illuminate\Session\Middleware\AuthenticateSession; use Illuminate\View\Middleware\ShareErrorsFromSession; @@ -25,13 +23,13 @@ use Illuminate\Auth\Middleware\AuthenticateWithBasicAuth; use Pterodactyl\Http\Middleware\Api\AuthenticateIPAccess; use Illuminate\Foundation\Http\Middleware\ValidatePostSize; -use Pterodactyl\Http\Middleware\Api\HandleStatelessRequest; use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse; use Pterodactyl\Http\Middleware\Api\Daemon\DaemonAuthenticate; use Pterodactyl\Http\Middleware\RequireTwoFactorAuthentication; use Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode; use Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull; use Pterodactyl\Http\Middleware\Api\Client\SubstituteClientBindings; +use Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful; use Pterodactyl\Http\Middleware\Api\Application\AuthenticateApplicationUser; class Kernel extends HttpKernel @@ -67,29 +65,19 @@ class Kernel extends HttpKernel RequireTwoFactorAuthentication::class, ], 'api' => [ - HandleStatelessRequest::class, IsValidJson::class, - StartSession::class, - AuthenticateSession::class, - VerifyCsrfToken::class, + EnsureFrontendRequestsAreStateful::class, + 'auth:sanctum', + RequireTwoFactorAuthentication::class, + AuthenticateIPAccess::class, ], 'application-api' => [ SubstituteBindings::class, - 'api..key:' . ApiKey::TYPE_APPLICATION, AuthenticateApplicationUser::class, - AuthenticateIPAccess::class, - ], - 'client-api' => [ - SubstituteClientBindings::class, - 'api..key:' . ApiKey::TYPE_ACCOUNT, - AuthenticateIPAccess::class, - // This is perhaps a little backwards with the Client API, but logically you'd be unable - // to create/get an API key without first enabling 2FA on the account, so I suppose in the - // end it makes sense. - // - // You just wouldn't be authenticating with the API by providing a 2FA token. - RequireTwoFactorAuthentication::class, ], + // TODO: don't allow an application key to use the client API, but do allow a client + // api key to access the application API. + 'client-api' => [SubstituteClientBindings::class], 'daemon' => [ SubstituteBindings::class, DaemonAuthenticate::class, @@ -112,7 +100,5 @@ class Kernel extends HttpKernel 'bindings' => SubstituteBindings::class, 'recaptcha' => VerifyReCaptcha::class, 'node.maintenance' => MaintenanceMiddleware::class, - // API Specific Middleware - 'api..key' => AuthenticateKey::class, ]; } diff --git a/app/Http/Middleware/Api/AuthenticateIPAccess.php b/app/Http/Middleware/Api/AuthenticateIPAccess.php index 2af34cfd9f..839bf83dd1 100644 --- a/app/Http/Middleware/Api/AuthenticateIPAccess.php +++ b/app/Http/Middleware/Api/AuthenticateIPAccess.php @@ -6,6 +6,7 @@ use IPTools\IP; use IPTools\Range; use Illuminate\Http\Request; +use Laravel\Sanctum\TransientToken; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; class AuthenticateIPAccess @@ -20,14 +21,19 @@ class AuthenticateIPAccess */ public function handle(Request $request, Closure $next) { - $model = $request->attributes->get('api_key'); + /** @var \Laravel\Sanctum\TransientToken|\Pterodactyl\Models\ApiKey $token */ + $token = $request->user()->currentAccessToken(); - if (is_null($model->allowed_ips) || empty($model->allowed_ips)) { + // If this is a stateful request just push the request through to the next + // middleware in the stack, there is nothing we need to explicitly check. If + // this is a valid API Key, but there is no allowed IP restriction, also pass + // the request through. + if ($token instanceof TransientToken || empty($token->allowed_ips)) { return $next($request); } $find = new IP($request->ip()); - foreach ($model->allowed_ips as $ip) { + foreach ($token->allowed_ips as $ip) { if (Range::parse($ip)->contains($find)) { return $next($request); } diff --git a/app/Http/Middleware/Api/AuthenticateKey.php b/app/Http/Middleware/Api/AuthenticateKey.php deleted file mode 100644 index 857bfab29e..0000000000 --- a/app/Http/Middleware/Api/AuthenticateKey.php +++ /dev/null @@ -1,109 +0,0 @@ -auth = $auth; - $this->encrypter = $encrypter; - $this->repository = $repository; - } - - /** - * Handle an API request by verifying that the provided API key is in a valid - * format and exists in the database. If there is currently a user in the session - * do not even bother to look at the token (they provided a cookie for this to - * be the case). - * - * @return mixed - * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - */ - public function handle(Request $request, Closure $next, int $keyType) - { - if (is_null($request->bearerToken()) && is_null($request->user())) { - throw new HttpException(401, 'A bearer token or valid user session cookie must be provided to access this endpoint.', null, ['WWW-Authenticate' => 'Bearer']); - } - - // This is a request coming through using cookies, we have an authenticated user - // not using an API key. Make some fake API key models and continue on through - // the process. - if ($request->user() instanceof User) { - $model = (new ApiKey())->forceFill([ - 'user_id' => $request->user()->id, - 'key_type' => ApiKey::TYPE_ACCOUNT, - ]); - } else { - $model = $this->authenticateApiKey($request->bearerToken(), $keyType); - - $this->auth->guard()->onceUsingId($model->user_id); - } - - $request->attributes->set('api_key', $model); - - return $next($request); - } - - /** - * Authenticate an API key. - * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - */ - protected function authenticateApiKey(string $key, int $keyType): ApiKey - { - $identifier = substr($key, 0, ApiKey::IDENTIFIER_LENGTH); - $token = substr($key, ApiKey::IDENTIFIER_LENGTH); - - try { - $model = $this->repository->findFirstWhere([ - ['identifier', '=', $identifier], - ['key_type', '=', $keyType], - ]); - } catch (RecordNotFoundException $exception) { - throw new AccessDeniedHttpException(); - } - - if (!hash_equals($this->encrypter->decrypt($model->token), $token)) { - throw new AccessDeniedHttpException(); - } - - $this->repository->withoutFreshModel()->update($model->id, ['last_used_at' => CarbonImmutable::now()]); - - return $model; - } -} diff --git a/app/Http/Middleware/Api/HandleStatelessRequest.php b/app/Http/Middleware/Api/HandleStatelessRequest.php deleted file mode 100644 index ab697d687b..0000000000 --- a/app/Http/Middleware/Api/HandleStatelessRequest.php +++ /dev/null @@ -1,35 +0,0 @@ -bearerToken()) && $request->isJson()) { - $request->session()->getHandler()->destroy( - $request->session()->getId() - ); - - $response->headers->remove('Set-Cookie'); - } - - return $response; - } -} diff --git a/app/Http/Middleware/VerifyCsrfToken.php b/app/Http/Middleware/VerifyCsrfToken.php index 6471814c68..ee5a3d216d 100644 --- a/app/Http/Middleware/VerifyCsrfToken.php +++ b/app/Http/Middleware/VerifyCsrfToken.php @@ -2,8 +2,6 @@ namespace Pterodactyl\Http\Middleware; -use Closure; -use Pterodactyl\Models\ApiKey; use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as BaseVerifier; class VerifyCsrfToken extends BaseVerifier @@ -16,31 +14,4 @@ class VerifyCsrfToken extends BaseVerifier * @var string[] */ protected $except = ['remote/*', 'daemon/*']; - - /** - * Manually apply CSRF protection to routes depending on the authentication - * mechanism being used. If the API request is using an API key that exists - * in the database we can safely ignore CSRF protections, since that would be - * a manually initiated request by a user or server. - * - * All other requests should go through the standard CSRF protections that - * Laravel affords us. This code will be removed in v2 since we have switched - * to using Sanctum for the API endpoints, which handles that for us automatically. - * - * @param \Illuminate\Http\Request $request - * - * @return mixed - * - * @throws \Illuminate\Session\TokenMismatchException - */ - public function handle($request, Closure $next) - { - $key = $request->attributes->get('api_key'); - - if ($key instanceof ApiKey && $key->exists) { - return $next($request); - } - - return parent::handle($request, $next); - } } diff --git a/app/Models/ApiKey.php b/app/Models/ApiKey.php index 4c23633331..32c6fa03e9 100644 --- a/app/Models/ApiKey.php +++ b/app/Models/ApiKey.php @@ -2,19 +2,59 @@ namespace Pterodactyl\Models; +use Illuminate\Support\Str; use Pterodactyl\Services\Acl\Api\AdminAcl; +use Illuminate\Database\Eloquent\Relations\BelongsTo; /** + * Pterodactyl\Models\ApiKey. + * * @property int $id * @property int $user_id * @property int $key_type - * @property string $identifier + * @property string|null $identifier * @property string $token - * @property array $allowed_ips - * @property string $memo - * @property \Carbon\Carbon|null $last_used_at - * @property \Carbon\Carbon $created_at - * @property \Carbon\Carbon $updated_at + * @property array|null $allowed_ips + * @property string|null $memo + * @property \Illuminate\Support\Carbon|null $last_used_at + * @property \Illuminate\Support\Carbon|null $created_at + * @property \Illuminate\Support\Carbon|null $updated_at + * @property int $r_servers + * @property int $r_nodes + * @property int $r_allocations + * @property int $r_users + * @property int $r_locations + * @property int $r_nests + * @property int $r_eggs + * @property int $r_database_hosts + * @property int $r_server_databases + * @property \Pterodactyl\Models\User $tokenable + * @property \Pterodactyl\Models\User $user + * + * @method static \Database\Factories\ApiKeyFactory factory(...$parameters) + * @method static \Illuminate\Database\Eloquent\Builder|ApiKey newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|ApiKey newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|ApiKey query() + * @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereAllowedIps($value) + * @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereId($value) + * @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereIdentifier($value) + * @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereKeyType($value) + * @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereLastUsedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereMemo($value) + * @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereRAllocations($value) + * @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereRDatabaseHosts($value) + * @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereREggs($value) + * @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereRLocations($value) + * @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereRNests($value) + * @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereRNodes($value) + * @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereRServerDatabases($value) + * @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereRServers($value) + * @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereRUsers($value) + * @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereToken($value) + * @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereUpdatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereUserId($value) + * @mixin \Eloquent */ class ApiKey extends Model { @@ -23,21 +63,21 @@ class ApiKey extends Model * API representation using fractal. */ public const RESOURCE_NAME = 'api_key'; - /** * Different API keys that can exist on the system. */ public const TYPE_NONE = 0; public const TYPE_ACCOUNT = 1; + /* @deprecated */ public const TYPE_APPLICATION = 2; + /* @deprecated */ public const TYPE_DAEMON_USER = 3; + /* @deprecated */ public const TYPE_DAEMON_APPLICATION = 4; - /** * The length of API key identifiers. */ public const IDENTIFIER_LENGTH = 16; - /** * The length of the actual API key that is encrypted and stored * in the database. @@ -124,4 +164,47 @@ class ApiKey extends Model self::UPDATED_AT, 'last_used_at', ]; + + /** + * Returns the user this token is assigned to. + */ + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } + + /** + * Required for support with Laravel Sanctum. + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + * + * @see \Laravel\Sanctum\Guard::supportsTokens() + */ + public function tokenable() + { + return $this->user(); + } + + /** + * Finds the model matching the provided token. + * + * @param string $token + * + * @return self|null + */ + public static function findToken($token) + { + $id = Str::substr($token, 0, self::IDENTIFIER_LENGTH); + $token = Str::substr($token, strlen($id)); + + return static::where('identifier', $id)->where('token', encrypt($token))->first(); + } + + /** + * Generates a new identifier for an API key. + */ + public static function generateTokenIdentifier(): string + { + return 'ptdl_' . Str::random(self::IDENTIFIER_LENGTH - 5); + } } diff --git a/app/Models/Traits/HasAccessTokens.php b/app/Models/Traits/HasAccessTokens.php new file mode 100644 index 0000000000..2aa21cb9e9 --- /dev/null +++ b/app/Models/Traits/HasAccessTokens.php @@ -0,0 +1,37 @@ +hasMany(Sanctum::$personalAccessTokenModel); + } + + public function createToken(string $name, array $abilities = ['*']) + { + /** @var \Pterodactyl\Models\ApiKey $token */ + $token = $this->tokens()->create([ + 'user_id' => $this->id, + 'key_type' => ApiKey::TYPE_ACCOUNT, + 'identifier' => ApiKey::generateTokenIdentifier(), + 'token' => encrypt($plain = Str::random(ApiKey::KEY_LENGTH)), + 'memo' => $name, + 'allowed_ips' => [], + ]); + + return new NewAccessToken($token, $token->identifier . $plain); + } +} diff --git a/app/Models/User.php b/app/Models/User.php index 196ad18ede..6e676a4163 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -3,6 +3,7 @@ namespace Pterodactyl\Models; use Pterodactyl\Rules\Username; +use Laravel\Sanctum\HasApiTokens; use Illuminate\Support\Collection; use Illuminate\Validation\Rules\In; use Illuminate\Auth\Authenticatable; @@ -18,7 +19,7 @@ use Pterodactyl\Notifications\SendPasswordReset as ResetPasswordNotification; /** - * \Pterodactyl\Models\User. + * Pterodactyl\Models\User. * * @property int $id * @property string|null $external_id @@ -28,27 +29,28 @@ * @property string|null $name_first * @property string|null $name_last * @property string $password - * @property string|null $remeber_token + * @property string|null $remember_token * @property string $language * @property bool $root_admin * @property bool $use_totp * @property string|null $totp_secret - * @property \Carbon\Carbon|null $totp_authenticated_at + * @property \Illuminate\Support\Carbon|null $totp_authenticated_at * @property bool $gravatar - * @property \Carbon\Carbon $created_at - * @property \Carbon\Carbon $updated_at - * @property string $name - * @property \Pterodactyl\Models\ApiKey[]|\Illuminate\Database\Eloquent\Collection $apiKeys - * @property \Pterodactyl\Models\Server[]|\Illuminate\Database\Eloquent\Collection $servers - * @property \Pterodactyl\Models\RecoveryToken[]|\Illuminate\Database\Eloquent\Collection $recoveryTokens - * @property string|null $remember_token + * @property \Illuminate\Support\Carbon|null $created_at + * @property \Illuminate\Support\Carbon|null $updated_at + * @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\ApiKey[] $apiKeys * @property int|null $api_keys_count + * @property string $name * @property \Illuminate\Notifications\DatabaseNotificationCollection|\Illuminate\Notifications\DatabaseNotification[] $notifications * @property int|null $notifications_count + * @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\RecoveryToken[] $recoveryTokens * @property int|null $recovery_tokens_count + * @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\Server[] $servers * @property int|null $servers_count * @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\UserSSHKey[] $sshKeys * @property int|null $ssh_keys_count + * @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\ApiKey[] $tokens + * @property int|null $tokens_count * * @method static \Database\Factories\UserFactory factory(...$parameters) * @method static Builder|User newModelQuery() @@ -82,6 +84,7 @@ class User extends Model implements use Authorizable; use AvailableLanguages; use CanResetPassword; + use HasApiTokens; use Notifiable; public const USER_LEVEL_USER = 0; diff --git a/app/Providers/AuthServiceProvider.php b/app/Providers/AuthServiceProvider.php index e147736ede..76ac26f376 100644 --- a/app/Providers/AuthServiceProvider.php +++ b/app/Providers/AuthServiceProvider.php @@ -2,6 +2,10 @@ namespace Pterodactyl\Providers; +use Laravel\Sanctum\Sanctum; +use Pterodactyl\Models\Server; +use Pterodactyl\Models\ApiKey; +use Pterodactyl\Policies\ServerPolicy; use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider; class AuthServiceProvider extends ServiceProvider @@ -12,7 +16,7 @@ class AuthServiceProvider extends ServiceProvider * @var array */ protected $policies = [ - 'Pterodactyl\Models\Server' => 'Pterodactyl\Policies\ServerPolicy', + Server::class => ServerPolicy::class, ]; /** @@ -20,6 +24,8 @@ class AuthServiceProvider extends ServiceProvider */ public function boot() { + Sanctum::usePersonalAccessTokenModel(ApiKey::class); + $this->registerPolicies(); } } diff --git a/composer.json b/composer.json index 8279aee77b..eb553e6a71 100644 --- a/composer.json +++ b/composer.json @@ -24,6 +24,7 @@ "laracasts/utilities": "~3.2.1", "laravel/framework": "^8.83", "laravel/helpers": "~1.5.0", + "laravel/sanctum": "~2.15.1", "laravel/tinker": "~2.7.2", "laravel/ui": "~3.4.5", "lcobucci/jwt": "~4.1.5", diff --git a/composer.lock b/composer.lock index db2b52ca1c..534c1958a8 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "59024efe671be95afe14319b19606566", + "content-hash": "0368e946c40456bcd1fb007bfc3e7bf0", "packages": [ { "name": "aws/aws-crt-php", @@ -1668,6 +1668,71 @@ }, "time": "2022-01-12T15:58:51+00:00" }, + { + "name": "laravel/sanctum", + "version": "v2.15.1", + "source": { + "type": "git", + "url": "https://github.com/laravel/sanctum.git", + "reference": "31fbe6f85aee080c4dc2f9b03dc6dd5d0ee72473" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/sanctum/zipball/31fbe6f85aee080c4dc2f9b03dc6dd5d0ee72473", + "reference": "31fbe6f85aee080c4dc2f9b03dc6dd5d0ee72473", + "shasum": "" + }, + "require": { + "ext-json": "*", + "illuminate/console": "^6.9|^7.0|^8.0|^9.0", + "illuminate/contracts": "^6.9|^7.0|^8.0|^9.0", + "illuminate/database": "^6.9|^7.0|^8.0|^9.0", + "illuminate/support": "^6.9|^7.0|^8.0|^9.0", + "php": "^7.2|^8.0" + }, + "require-dev": { + "mockery/mockery": "^1.0", + "orchestra/testbench": "^4.0|^5.0|^6.0|^7.0", + "phpunit/phpunit": "^8.0|^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + }, + "laravel": { + "providers": [ + "Laravel\\Sanctum\\SanctumServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Laravel\\Sanctum\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Laravel Sanctum provides a featherweight authentication system for SPAs and simple APIs.", + "keywords": [ + "auth", + "laravel", + "sanctum" + ], + "support": { + "issues": "https://github.com/laravel/sanctum/issues", + "source": "https://github.com/laravel/sanctum" + }, + "time": "2022-04-08T13:39:49+00:00" + }, { "name": "laravel/serializable-closure", "version": "v1.1.1", @@ -10773,5 +10838,5 @@ "platform-overrides": { "php": "7.4.0" }, - "plugin-api-version": "2.2.0" + "plugin-api-version": "2.3.0" } diff --git a/config/sanctum.php b/config/sanctum.php new file mode 100644 index 0000000000..5e2a120625 --- /dev/null +++ b/config/sanctum.php @@ -0,0 +1,67 @@ + explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf( + '%s%s', + 'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1', + Sanctum::currentApplicationUrlWithPort() + ))), + + /* + |-------------------------------------------------------------------------- + | Sanctum Guards + |-------------------------------------------------------------------------- + | + | This array contains the authentication guards that will be checked when + | Sanctum is trying to authenticate a request. If none of these guards + | are able to authenticate the request, Sanctum will use the bearer + | token that's present on an incoming request for authentication. + | + */ + + 'guard' => ['web'], + + /* + |-------------------------------------------------------------------------- + | Expiration Minutes + |-------------------------------------------------------------------------- + | + | This value controls the number of minutes until an issued token will be + | considered expired. If this value is null, personal access tokens do + | not expire. This won't tweak the lifetime of first-party sessions. + | + */ + + 'expiration' => null, + + /* + |-------------------------------------------------------------------------- + | Sanctum Middleware + |-------------------------------------------------------------------------- + | + | When authenticating your first-party SPA with Sanctum you may need to + | customize some of the middleware Sanctum uses while processing the + | request. You may change the middleware listed below as required. + | + */ + + 'middleware' => [ + 'verify_csrf_token' => Pterodactyl\Http\Middleware\VerifyCsrfToken::class, + 'encrypt_cookies' => Pterodactyl\Http\Middleware\EncryptCookies::class, + ], + +]; From e9c633fd03222968d175312a90122c123a74374b Mon Sep 17 00:00:00 2001 From: DaneEveritt Date: Sun, 22 May 2022 15:37:39 -0400 Subject: [PATCH 045/458] Update transformers and controllers to no longer pull an API key attribute --- .../Application/ApplicationApiController.php | 17 +++-- .../Api/Client/ClientApiController.php | 25 ++++--- .../Api/Application/ApplicationApiRequest.php | 16 ++-- .../Api/Application/BaseTransformer.php | 64 +++++++++------- .../Api/Client/BaseClientTransformer.php | 40 ++-------- .../Api/Client/DatabaseTransformer.php | 2 +- .../Api/Client/ServerTransformer.php | 15 ++-- .../ApplicationApiIntegrationTestCase.php | 10 ++- .../Api/AuthenticateIPAccessTest.php | 73 ------------------- 9 files changed, 90 insertions(+), 172 deletions(-) delete mode 100644 tests/Unit/Http/Middleware/Api/AuthenticateIPAccessTest.php diff --git a/app/Http/Controllers/Api/Application/ApplicationApiController.php b/app/Http/Controllers/Api/Application/ApplicationApiController.php index 6078b6f2c5..91da818937 100644 --- a/app/Http/Controllers/Api/Application/ApplicationApiController.php +++ b/app/Http/Controllers/Api/Application/ApplicationApiController.php @@ -55,17 +55,20 @@ public function loadDependencies(Fractal $fractal, Request $request) /** * Return an instance of an application transformer. * - * @return \Pterodactyl\Transformers\Api\Application\BaseTransformer + * @template T of \Pterodactyl\Transformers\Api\Application\BaseTransformer + * + * @param class-string $abstract + * + * @return T + * + * @noinspection PhpDocSignatureInspection + * @noinspection PhpUndefinedClassInspection */ public function getTransformer(string $abstract) { - /** @var \Pterodactyl\Transformers\Api\Application\BaseTransformer $transformer */ - $transformer = Container::getInstance()->make($abstract); - $transformer->setKey($this->request->attributes->get('api_key')); - - Assert::isInstanceOf($transformer, BaseTransformer::class); + Assert::subclassOf($abstract, BaseTransformer::class); - return $transformer; + return $abstract::fromRequest($this->request); } /** diff --git a/app/Http/Controllers/Api/Client/ClientApiController.php b/app/Http/Controllers/Api/Client/ClientApiController.php index c7f6f1a49c..210489dea0 100644 --- a/app/Http/Controllers/Api/Client/ClientApiController.php +++ b/app/Http/Controllers/Api/Client/ClientApiController.php @@ -3,7 +3,6 @@ namespace Pterodactyl\Http\Controllers\Api\Client; use Webmozart\Assert\Assert; -use Illuminate\Container\Container; use Pterodactyl\Transformers\Daemon\BaseDaemonTransformer; use Pterodactyl\Transformers\Api\Client\BaseClientTransformer; use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController; @@ -45,21 +44,23 @@ protected function parseIncludes() /** * Return an instance of an application transformer. * - * @return \Pterodactyl\Transformers\Api\Client\BaseClientTransformer + * @template T of \Pterodactyl\Transformers\Api\Client\BaseClientTransformer + * + * @param class-string $abstract + * + * @return T + * + * @noinspection PhpUndefinedClassInspection + * @noinspection PhpDocSignatureInspection */ public function getTransformer(string $abstract) { - /** @var \Pterodactyl\Transformers\Api\Client\BaseClientTransformer $transformer */ - $transformer = Container::getInstance()->make($abstract); - Assert::isInstanceOfAny($transformer, [ - BaseClientTransformer::class, - BaseDaemonTransformer::class, - ]); + Assert::methodExists($abstract, 'fromRequest'); - if ($transformer instanceof BaseClientTransformer) { - $transformer->setKey($this->request->attributes->get('api_key')); - $transformer->setUser($this->request->user()); - } + /** @var T $transformer */ + $transformer = $abstract::fromRequest($this->request); + + Assert::isInstanceOfAny($transformer, [BaseClientTransformer::class, BaseDaemonTransformer::class]); return $transformer; } diff --git a/app/Http/Requests/Api/Application/ApplicationApiRequest.php b/app/Http/Requests/Api/Application/ApplicationApiRequest.php index 5dc903f533..ec7643bd52 100644 --- a/app/Http/Requests/Api/Application/ApplicationApiRequest.php +++ b/app/Http/Requests/Api/Application/ApplicationApiRequest.php @@ -3,6 +3,7 @@ namespace Pterodactyl\Http\Requests\Api\Application; use Webmozart\Assert\Assert; +use Laravel\Sanctum\TransientToken; use Illuminate\Validation\Validator; use Illuminate\Database\Eloquent\Model; use Pterodactyl\Services\Acl\Api\AdminAcl; @@ -11,14 +12,6 @@ abstract class ApplicationApiRequest extends FormRequest { - /** - * Tracks if the request has been validated internally or not to avoid - * making duplicate validation calls. - * - * @var bool - */ - private $hasValidated = false; - /** * The resource that should be checked when performing the authorization * function for this request. @@ -47,7 +40,12 @@ public function authorize(): bool throw new PterodactylException('An ACL resource must be defined on API requests.'); } - return AdminAcl::check($this->attributes->get('api_key'), $this->resource, $this->permission); + $token = $this->user()->currentAccessToken(); + if ($token instanceof TransientToken) { + return true; + } + + return AdminAcl::check($token, $this->resource, $this->permission); } /** diff --git a/app/Transformers/Api/Application/BaseTransformer.php b/app/Transformers/Api/Application/BaseTransformer.php index 3c5630366d..bb8f5ce9b1 100644 --- a/app/Transformers/Api/Application/BaseTransformer.php +++ b/app/Transformers/Api/Application/BaseTransformer.php @@ -3,12 +3,13 @@ namespace Pterodactyl\Transformers\Api\Application; use Carbon\CarbonImmutable; +use Illuminate\Http\Request; +use Webmozart\Assert\Assert; use Pterodactyl\Models\ApiKey; use Illuminate\Container\Container; use Illuminate\Database\Eloquent\Model; use League\Fractal\TransformerAbstract; use Pterodactyl\Services\Acl\Api\AdminAcl; -use Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException; /** * @method array transform(Model $model) @@ -17,15 +18,7 @@ abstract class BaseTransformer extends TransformerAbstract { public const RESPONSE_TIMEZONE = 'UTC'; - /** - * @var \Pterodactyl\Models\ApiKey - */ - private $key; - - /** - * Return the resource name for the JSONAPI output. - */ - abstract public function getResourceName(): string; + protected Request $request; /** * BaseTransformer constructor. @@ -39,54 +32,69 @@ public function __construct() } /** - * Set the HTTP request class being used for this request. + * Return the resource name for the JSONAPI output. + */ + abstract public function getResourceName(): string; + + /** + * Sets the request on the instance. * - * @return $this + * @return static */ - public function setKey(ApiKey $key) + public function setRequest(Request $request): self { - $this->key = $key; + $this->request = $request; return $this; } /** - * Return the request instance being used for this transformer. + * Returns a new transformer instance with the request set on the instance. + * + * @return \Pterodactyl\Transformers\Api\Application\BaseTransformer */ - public function getKey(): ApiKey + public static function fromRequest(Request $request) { - return $this->key; + return app(static::class)->setRequest($request); } /** * Determine if the API key loaded onto the transformer has permission * to access a different resource. This is used when including other * models on a transformation request. + * + * @deprecated — prefer $user->can/cannot methods */ protected function authorize(string $resource): bool { - return AdminAcl::check($this->getKey(), $resource, AdminAcl::READ); + $token = $this->request->user()->currentAccessToken(); + if (!$token instanceof ApiKey || $token->key_type !== ApiKey::TYPE_APPLICATION) { + return false; + } + + return AdminAcl::check($token, $resource, AdminAcl::READ); } /** * Create a new instance of the transformer and pass along the currently * set API key. * - * @return \Pterodactyl\Transformers\Api\Application\BaseTransformer + * @template T of \Pterodactyl\Transformers\Api\Application\BaseTransformer + * + * @param class-string $abstract + * + * @return T * * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException + * + * @noinspection PhpUndefinedClassInspection + * @noinspection PhpDocSignatureInspection */ - protected function makeTransformer(string $abstract, array $parameters = []) + protected function makeTransformer(string $abstract) { - /** @var \Pterodactyl\Transformers\Api\Application\BaseTransformer $transformer */ - $transformer = Container::getInstance()->makeWith($abstract, $parameters); - $transformer->setKey($this->getKey()); - - if (!$transformer instanceof self) { - throw new InvalidTransformerLevelException('Calls to ' . __METHOD__ . ' must return a transformer that is an instance of ' . __CLASS__); - } + Assert::subclassOf($abstract, self::class); - return $transformer; + return $abstract::fromRequest($this->request); } /** diff --git a/app/Transformers/Api/Client/BaseClientTransformer.php b/app/Transformers/Api/Client/BaseClientTransformer.php index efe53276f3..0388effb08 100644 --- a/app/Transformers/Api/Client/BaseClientTransformer.php +++ b/app/Transformers/Api/Client/BaseClientTransformer.php @@ -5,31 +5,16 @@ use Pterodactyl\Models\User; use Webmozart\Assert\Assert; use Pterodactyl\Models\Server; -use Illuminate\Container\Container; -use Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException; use Pterodactyl\Transformers\Api\Application\BaseTransformer as BaseApplicationTransformer; abstract class BaseClientTransformer extends BaseApplicationTransformer { - /** - * @var \Pterodactyl\Models\User - */ - private $user; - /** * Return the user model of the user requesting this transformation. */ public function getUser(): User { - return $this->user; - } - - /** - * Set the user model of the user requesting this transformation. - */ - public function setUser(User $user) - { - $this->user = $user; + return $this->request->user(); } /** @@ -37,33 +22,22 @@ public function setUser(User $user) * to access a different resource. This is used when including other * models on a transformation request. * - * @param \Pterodactyl\Models\Server $server + * @noinspection PhpParameterNameChangedDuringInheritanceInspection */ protected function authorize(string $ability, Server $server = null): bool { Assert::isInstanceOf($server, Server::class); - return $this->getUser()->can($ability, [$server]); + return $this->request->user()->can($ability, [$server]); } /** - * Create a new instance of the transformer and pass along the currently - * set API key. - * - * @return self - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException + * {@inheritDoc} */ - protected function makeTransformer(string $abstract, array $parameters = []) + protected function makeTransformer(string $abstract) { - /** @var \Pterodactyl\Transformers\Api\Application\BaseTransformer $transformer */ - $transformer = Container::getInstance()->makeWith($abstract, $parameters); - $transformer->setKey($this->getKey()); - - if (!$transformer instanceof self) { - throw new InvalidTransformerLevelException('Calls to ' . __METHOD__ . ' must return a transformer that is an instance of ' . __CLASS__); - } + Assert::subclassOf($abstract, self::class); - return $transformer; + return parent::makeTransformer($abstract); } } diff --git a/app/Transformers/Api/Client/DatabaseTransformer.php b/app/Transformers/Api/Client/DatabaseTransformer.php index 4857ea0272..bdabbeed5f 100644 --- a/app/Transformers/Api/Client/DatabaseTransformer.php +++ b/app/Transformers/Api/Client/DatabaseTransformer.php @@ -59,7 +59,7 @@ public function transform(Database $model): array */ public function includePassword(Database $database) { - if (!$this->getUser()->can(Permission::ACTION_DATABASE_VIEW_PASSWORD, $database->server)) { + if (!$this->request->user()->can(Permission::ACTION_DATABASE_VIEW_PASSWORD, $database->server)) { return $this->null(); } diff --git a/app/Transformers/Api/Client/ServerTransformer.php b/app/Transformers/Api/Client/ServerTransformer.php index 583ccab686..53f4adf4ba 100644 --- a/app/Transformers/Api/Client/ServerTransformer.php +++ b/app/Transformers/Api/Client/ServerTransformer.php @@ -34,8 +34,10 @@ public function transform(Server $server): array /** @var \Pterodactyl\Services\Servers\StartupCommandService $service */ $service = Container::getInstance()->make(StartupCommandService::class); + $user = $this->request->user(); + return [ - 'server_owner' => $this->getKey()->user_id === $server->owner_id, + 'server_owner' => $user->id === $server->owner_id, 'identifier' => $server->uuidShort, 'internal_id' => $server->id, 'uuid' => $server->uuid, @@ -55,7 +57,7 @@ public function transform(Server $server): array 'threads' => $server->threads, 'oom_disabled' => $server->oom_disabled, ], - 'invocation' => $service->handle($server, !$this->getUser()->can(Permission::ACTION_STARTUP_READ, $server)), + 'invocation' => $service->handle($server, !$user->can(Permission::ACTION_STARTUP_READ, $server)), 'docker_image' => $server->image, 'egg_features' => $server->egg->inherit_features, 'feature_limits' => [ @@ -75,7 +77,7 @@ public function transform(Server $server): array /** * Returns the allocations associated with this server. * - * @return \League\Fractal\Resource\Collection|\League\Fractal\Resource\NullResource + * @return \League\Fractal\Resource\Collection * * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ @@ -83,6 +85,7 @@ public function includeAllocations(Server $server) { $transformer = $this->makeTransformer(AllocationTransformer::class); + $user = $this->request->user(); // While we include this permission, we do need to actually handle it slightly different here // for the purpose of keeping things functionally working. If the user doesn't have read permissions // for the allocations we'll only return the primary server allocation, and any notes associated @@ -90,7 +93,7 @@ public function includeAllocations(Server $server) // // This allows us to avoid too much permission regression, without also hiding information that // is generally needed for the frontend to make sense when browsing or searching results. - if (!$this->getUser()->can(Permission::ACTION_ALLOCATION_READ, $server)) { + if (!$user->can(Permission::ACTION_ALLOCATION_READ, $server)) { $primary = clone $server->allocation; $primary->notes = null; @@ -107,7 +110,7 @@ public function includeAllocations(Server $server) */ public function includeVariables(Server $server) { - if (!$this->getUser()->can(Permission::ACTION_STARTUP_READ, $server)) { + if (!$this->request->user()->can(Permission::ACTION_STARTUP_READ, $server)) { return $this->null(); } @@ -139,7 +142,7 @@ public function includeEgg(Server $server) */ public function includeSubusers(Server $server) { - if (!$this->getUser()->can(Permission::ACTION_USER_READ, $server)) { + if (!$this->request->user()->can(Permission::ACTION_USER_READ, $server)) { return $this->null(); } diff --git a/tests/Integration/Api/Application/ApplicationApiIntegrationTestCase.php b/tests/Integration/Api/Application/ApplicationApiIntegrationTestCase.php index daca172833..8220f02526 100644 --- a/tests/Integration/Api/Application/ApplicationApiIntegrationTestCase.php +++ b/tests/Integration/Api/Application/ApplicationApiIntegrationTestCase.php @@ -3,6 +3,7 @@ namespace Pterodactyl\Tests\Integration\Api\Application; use Pterodactyl\Models\User; +use Illuminate\Http\Request; use PHPUnit\Framework\Assert; use Pterodactyl\Models\ApiKey; use Pterodactyl\Services\Acl\Api\AdminAcl; @@ -110,9 +111,12 @@ protected function createApiKey(User $user, array $permissions = []): ApiKey */ protected function getTransformer(string $abstract): BaseTransformer { - /** @var \Pterodactyl\Transformers\Api\Application\BaseTransformer $transformer */ - $transformer = $this->app->make($abstract); - $transformer->setKey($this->getApiKey()); + $request = Request::createFromGlobals(); + $request->setUserResolver(function () { + return $this->getApiKey()->user; + }); + + $transformer = $abstract::fromRequest($request); Assert::assertInstanceOf(BaseTransformer::class, $transformer); Assert::assertNotInstanceOf(BaseClientTransformer::class, $transformer); diff --git a/tests/Unit/Http/Middleware/Api/AuthenticateIPAccessTest.php b/tests/Unit/Http/Middleware/Api/AuthenticateIPAccessTest.php deleted file mode 100644 index c362e9cca7..0000000000 --- a/tests/Unit/Http/Middleware/Api/AuthenticateIPAccessTest.php +++ /dev/null @@ -1,73 +0,0 @@ -make(['allowed_ips' => []]); - $this->setRequestAttribute('api_key', $model); - - $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); - } - - /** - * Test middleware works correctly when a valid IP accesses - * and there is an IP restriction. - */ - public function testWithValidIP() - { - $model = ApiKey::factory()->make(['allowed_ips' => ['127.0.0.1']]); - $this->setRequestAttribute('api_key', $model); - - $this->request->shouldReceive('ip')->withNoArgs()->once()->andReturn('127.0.0.1'); - - $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); - } - - /** - * Test that a CIDR range can be used. - */ - public function testValidIPAgainstCIDRRange() - { - $model = ApiKey::factory()->make(['allowed_ips' => ['192.168.1.1/28']]); - $this->setRequestAttribute('api_key', $model); - - $this->request->shouldReceive('ip')->withNoArgs()->once()->andReturn('192.168.1.15'); - - $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); - } - - /** - * Test that an exception is thrown when an invalid IP address - * tries to connect and there is an IP restriction. - */ - public function testWithInvalidIP() - { - $this->expectException(AccessDeniedHttpException::class); - - $model = ApiKey::factory()->make(['allowed_ips' => ['127.0.0.1']]); - $this->setRequestAttribute('api_key', $model); - - $this->request->shouldReceive('ip')->withNoArgs()->twice()->andReturn('127.0.0.2'); - - $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); - } - - /** - * Return an instance of the middleware to be used when testing. - */ - private function getMiddleware(): AuthenticateIPAccess - { - return new AuthenticateIPAccess(); - } -} From f7fc67344e1397f5499322aeda6b3f188238381d Mon Sep 17 00:00:00 2001 From: DaneEveritt Date: Sun, 22 May 2022 16:05:58 -0400 Subject: [PATCH 046/458] Ensure tokens are found in the database using the expected logic --- app/Exceptions/Handler.php | 18 +++++++++++++++-- .../Api/Client/AccountController.php | 16 +++++++++------ app/Models/ApiKey.php | 8 ++++++-- database/Factories/ApiKeyFactory.php | 2 +- .../ApplicationApiIntegrationTestCase.php | 20 ++++++------------- 5 files changed, 39 insertions(+), 25 deletions(-) diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index a94ad8aad2..55c1d88314 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -47,6 +47,18 @@ class Handler extends ExceptionHandler ValidationException::class, ]; + /** + * Maps exceptions to a specific response code. This handles special exception + * types that don't have a defined response code. + * + * @var array + */ + protected static array $exceptionResponseCodes = [ + AuthenticationException::class => 401, + AuthorizationException::class => 403, + ValidationException::class => 422, + ]; + /** * A list of the inputs that are never flashed for validation exceptions. * @@ -187,12 +199,14 @@ public function invalidJson($request, ValidationException $exception) */ public static function convertToArray(Throwable $exception, array $override = []): array { + $match = self::$exceptionResponseCodes[get_class($exception)] ?? null; + $error = [ 'code' => class_basename($exception), 'status' => method_exists($exception, 'getStatusCode') ? strval($exception->getStatusCode()) - : ($exception instanceof ValidationException ? '422' : '500'), - 'detail' => $exception instanceof HttpExceptionInterface + : strval($match ?? '500'), + 'detail' => $exception instanceof HttpExceptionInterface || !is_null($match) ? $exception->getMessage() : 'An unexpected error was encountered while processing this request, please try again.', ]; diff --git a/app/Http/Controllers/Api/Client/AccountController.php b/app/Http/Controllers/Api/Client/AccountController.php index ead296d3ca..963c01374f 100644 --- a/app/Http/Controllers/Api/Client/AccountController.php +++ b/app/Http/Controllers/Api/Client/AccountController.php @@ -19,19 +19,19 @@ class AccountController extends ClientApiController private $updateService; /** - * @var \Illuminate\Auth\SessionGuard + * @var \Illuminate\Auth\AuthManager */ - private $sessionGuard; + private $manager; /** * AccountController constructor. */ - public function __construct(AuthManager $sessionGuard, UserUpdateService $updateService) + public function __construct(AuthManager $manager, UserUpdateService $updateService) { parent::__construct(); $this->updateService = $updateService; - $this->sessionGuard = $sessionGuard; + $this->manager = $manager; } public function index(Request $request): array @@ -64,13 +64,17 @@ public function updatePassword(UpdatePasswordRequest $request): JsonResponse { $user = $this->updateService->handle($request->user(), $request->validated()); + $guard = $this->manager->guard(); // If you do not update the user in the session you'll end up working with a // cached copy of the user that does not include the updated password. Do this // to correctly store the new user details in the guard and allow the logout // other devices functionality to work. - $this->sessionGuard->setUser($user); + $guard->setUser($user); - $this->sessionGuard->logoutOtherDevices($request->input('password')); + // This method doesn't exist in the stateless Sanctum world. + if (method_exists($guard, 'logoutOtherDevices')) { + $guard->logoutOtherDevices($request->input('password')); + } return new JsonResponse([], Response::HTTP_NO_CONTENT); } diff --git a/app/Models/ApiKey.php b/app/Models/ApiKey.php index 32c6fa03e9..cd5a0ddcae 100644 --- a/app/Models/ApiKey.php +++ b/app/Models/ApiKey.php @@ -195,9 +195,13 @@ public function tokenable() public static function findToken($token) { $id = Str::substr($token, 0, self::IDENTIFIER_LENGTH); - $token = Str::substr($token, strlen($id)); - return static::where('identifier', $id)->where('token', encrypt($token))->first(); + $model = static::where('identifier', $id)->first(); + if (!is_null($model) && decrypt($model->token) === Str::substr($token, strlen($id))) { + return $model; + } + + return null; } /** diff --git a/database/Factories/ApiKeyFactory.php b/database/Factories/ApiKeyFactory.php index 1faa4be553..77795e6075 100644 --- a/database/Factories/ApiKeyFactory.php +++ b/database/Factories/ApiKeyFactory.php @@ -25,7 +25,7 @@ public function definition(): array return [ 'key_type' => ApiKey::TYPE_APPLICATION, - 'identifier' => Str::random(ApiKey::IDENTIFIER_LENGTH), + 'identifier' => ApiKey::generateTokenIdentifier(), 'token' => $token ?: $token = encrypt(Str::random(ApiKey::KEY_LENGTH)), 'allowed_ips' => null, 'memo' => 'Test Function Key', diff --git a/tests/Integration/Api/Application/ApplicationApiIntegrationTestCase.php b/tests/Integration/Api/Application/ApplicationApiIntegrationTestCase.php index 8220f02526..bc919cdc3f 100644 --- a/tests/Integration/Api/Application/ApplicationApiIntegrationTestCase.php +++ b/tests/Integration/Api/Application/ApplicationApiIntegrationTestCase.php @@ -2,8 +2,8 @@ namespace Pterodactyl\Tests\Integration\Api\Application; -use Pterodactyl\Models\User; use Illuminate\Http\Request; +use Pterodactyl\Models\User; use PHPUnit\Framework\Assert; use Pterodactyl\Models\ApiKey; use Pterodactyl\Services\Acl\Api\AdminAcl; @@ -41,10 +41,9 @@ public function setUp(): void $this->user = $this->createApiUser(); $this->key = $this->createApiKey($this->user); - $this->withHeader('Accept', 'application/vnd.pterodactyl.v1+json'); - $this->withHeader('Authorization', 'Bearer ' . $this->getApiKey()->identifier . decrypt($this->getApiKey()->token)); - - $this->withMiddleware('api..key:' . ApiKey::TYPE_APPLICATION); + $this + ->withHeader('Accept', 'application/vnd.pterodactyl.v1+json') + ->withHeader('Authorization', 'Bearer ' . $this->key->identifier . decrypt($this->key->token)); } public function getApiUser(): User @@ -63,17 +62,10 @@ public function getApiKey(): ApiKey protected function createNewDefaultApiKey(User $user, array $permissions = []): ApiKey { $this->key = $this->createApiKey($user, $permissions); - $this->refreshHeaders($this->key); - return $this->key; - } + $this->withHeader('Authorization', 'Bearer ' . $this->key->identifier . decrypt($this->key->token)); - /** - * Refresh the authorization header for a request to use a different API key. - */ - protected function refreshHeaders(ApiKey $key) - { - $this->withHeader('Authorization', 'Bearer ' . $key->identifier . decrypt($key->token)); + return $this->key; } /** From 33bafe9277dfea8684db5848b15fa8607a452ace Mon Sep 17 00:00:00 2001 From: DaneEveritt Date: Sun, 22 May 2022 16:23:22 -0400 Subject: [PATCH 047/458] Simplify transformer logic --- app/Exceptions/DisplayException.php | 10 ++---- app/Exceptions/Handler.php | 34 ++++++++++++++----- .../Api/Client/ClientApiController.php | 10 ++---- .../Api/Client/Servers/FileController.php | 2 +- .../Client}/FileObjectTransformer.php | 11 ++---- .../Daemon/BaseDaemonTransformer.php | 9 ----- 6 files changed, 32 insertions(+), 44 deletions(-) rename app/Transformers/{Daemon => Api/Client}/FileObjectTransformer.php (80%) delete mode 100644 app/Transformers/Daemon/BaseDaemonTransformer.php diff --git a/app/Exceptions/DisplayException.php b/app/Exceptions/DisplayException.php index 063340a726..636b9dfc94 100644 --- a/app/Exceptions/DisplayException.php +++ b/app/Exceptions/DisplayException.php @@ -58,17 +58,11 @@ public function getStatusCode() * * @param \Illuminate\Http\Request $request * - * @return \Illuminate\Http\JsonResponse|\Illuminate\Http\RedirectResponse + * @return \Illuminate\Http\RedirectResponse */ public function render($request) { - if ($request->expectsJson()) { - return response()->json(Handler::convertToArray($this, [ - 'detail' => $this->getMessage(), - ]), method_exists($this, 'getStatusCode') ? $this->getStatusCode() : Response::HTTP_BAD_REQUEST); - } - - Container::getInstance()->make(AlertsMessageBag::class)->danger($this->getMessage())->flash(); + app(AlertsMessageBag::class)->danger($this->getMessage())->flash(); return redirect()->back()->withInput(); } diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index 55c1d88314..6ec987c990 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -197,7 +197,7 @@ public function invalidJson($request, ValidationException $exception) /** * Return the exception as a JSONAPI representation for use on API requests. */ - public static function convertToArray(Throwable $exception, array $override = []): array + protected function convertExceptionToArray(Throwable $exception, array $override = []): array { $match = self::$exceptionResponseCodes[get_class($exception)] ?? null; @@ -226,7 +226,13 @@ public static function convertToArray(Throwable $exception, array $override = [] 'file' => str_replace(Application::getInstance()->basePath(), '', $exception->getFile()), ], 'meta' => [ - 'trace' => explode("\n", $exception->getTraceAsString()), + 'trace' => Collection::make($exception->getTrace()) + ->map(fn ($trace) => Arr::except($trace, ['args'])) + ->all(), + 'previous' => Collection::make($this->extractPrevious($exception)) + ->map(fn ($exception) => $exception->getTrace()) + ->map(fn ($trace) => Arr::except($trace, ['args'])) + ->all(), ], ]); } @@ -252,20 +258,30 @@ public static function isReportable(Exception $exception): bool protected function unauthenticated($request, AuthenticationException $exception) { if ($request->expectsJson()) { - return new JsonResponse(self::convertToArray($exception), JsonResponse::HTTP_UNAUTHORIZED); + return new JsonResponse($this->convertExceptionToArray($exception), JsonResponse::HTTP_UNAUTHORIZED); } - return $this->container->make('redirect')->guest('/auth/login'); + return redirect()->guest('/auth/login'); } /** - * Converts an exception into an array to render in the response. Overrides - * Laravel's built-in converter to output as a JSONAPI spec compliant object. + * Extracts all of the previous exceptions that lead to the one passed into this + * function being thrown. * - * @return array + * @param \Throwable $e + * @return \Throwable[] */ - protected function convertExceptionToArray(Throwable $exception) + protected function extractPrevious(Throwable $e): array { - return self::convertToArray($exception); + $previous = []; + while ($value = $e->getPrevious()) { + if (!$value instanceof Throwable) { + break; + } + $previous[] = $value; + $e = $value; + } + + return $previous; } } diff --git a/app/Http/Controllers/Api/Client/ClientApiController.php b/app/Http/Controllers/Api/Client/ClientApiController.php index 210489dea0..56e6a17147 100644 --- a/app/Http/Controllers/Api/Client/ClientApiController.php +++ b/app/Http/Controllers/Api/Client/ClientApiController.php @@ -3,7 +3,6 @@ namespace Pterodactyl\Http\Controllers\Api\Client; use Webmozart\Assert\Assert; -use Pterodactyl\Transformers\Daemon\BaseDaemonTransformer; use Pterodactyl\Transformers\Api\Client\BaseClientTransformer; use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController; @@ -55,13 +54,8 @@ protected function parseIncludes() */ public function getTransformer(string $abstract) { - Assert::methodExists($abstract, 'fromRequest'); + Assert::subclassOf($abstract, BaseClientTransformer::class); - /** @var T $transformer */ - $transformer = $abstract::fromRequest($this->request); - - Assert::isInstanceOfAny($transformer, [BaseClientTransformer::class, BaseDaemonTransformer::class]); - - return $transformer; + return $abstract::fromRequest($this->request); } } diff --git a/app/Http/Controllers/Api/Client/Servers/FileController.php b/app/Http/Controllers/Api/Client/Servers/FileController.php index 9ff503fd7b..5a106e037a 100644 --- a/app/Http/Controllers/Api/Client/Servers/FileController.php +++ b/app/Http/Controllers/Api/Client/Servers/FileController.php @@ -10,7 +10,7 @@ use Pterodactyl\Services\Nodes\NodeJWTService; use Illuminate\Contracts\Routing\ResponseFactory; use Pterodactyl\Repositories\Wings\DaemonFileRepository; -use Pterodactyl\Transformers\Daemon\FileObjectTransformer; +use Pterodactyl\Transformers\Api\Client\FileObjectTransformer; use Pterodactyl\Http\Controllers\Api\Client\ClientApiController; use Pterodactyl\Http\Requests\Api\Client\Servers\Files\CopyFileRequest; use Pterodactyl\Http\Requests\Api\Client\Servers\Files\PullFileRequest; diff --git a/app/Transformers/Daemon/FileObjectTransformer.php b/app/Transformers/Api/Client/FileObjectTransformer.php similarity index 80% rename from app/Transformers/Daemon/FileObjectTransformer.php rename to app/Transformers/Api/Client/FileObjectTransformer.php index 1ad86dce86..634a7d46e7 100644 --- a/app/Transformers/Daemon/FileObjectTransformer.php +++ b/app/Transformers/Api/Client/FileObjectTransformer.php @@ -1,19 +1,12 @@ Date: Sun, 22 May 2022 16:50:36 -0400 Subject: [PATCH 048/458] Mark a request as being stateful if a cookie for the session is provided at all This accounts for poorly configured API clients that try to use cookies for authentication purposes. Treat everything with a session cookie as being a stateful request from the front-end. --- app/Exceptions/Handler.php | 4 +- app/Http/Kernel.php | 4 +- .../Middleware/EnsureStatefulRequests.php | 57 +++++++++++++++++++ 3 files changed, 61 insertions(+), 4 deletions(-) create mode 100644 app/Http/Middleware/EnsureStatefulRequests.php diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index 6ec987c990..2155f6c3db 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -179,9 +179,9 @@ public function invalidJson($request, ValidationException $exception) )), ]; - $converted = self::convertToArray($exception)['errors'][0]; + $converted = $this->convertExceptionToArray($exception)['errors'][0]; $converted['detail'] = $error; - $converted['meta'] = is_array($converted['meta'] ?? null) ? array_merge($converted['meta'], $meta) : $meta; + $converted['meta'] = array_merge($converted['meta'] ?? [], $meta); $response[] = $converted; } diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index fe924119fa..622a8324da 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -19,6 +19,7 @@ use Illuminate\Session\Middleware\AuthenticateSession; use Illuminate\View\Middleware\ShareErrorsFromSession; use Pterodactyl\Http\Middleware\MaintenanceMiddleware; +use Pterodactyl\Http\Middleware\EnsureStatefulRequests; use Pterodactyl\Http\Middleware\RedirectIfAuthenticated; use Illuminate\Auth\Middleware\AuthenticateWithBasicAuth; use Pterodactyl\Http\Middleware\Api\AuthenticateIPAccess; @@ -29,7 +30,6 @@ use Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode; use Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull; use Pterodactyl\Http\Middleware\Api\Client\SubstituteClientBindings; -use Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful; use Pterodactyl\Http\Middleware\Api\Application\AuthenticateApplicationUser; class Kernel extends HttpKernel @@ -66,7 +66,7 @@ class Kernel extends HttpKernel ], 'api' => [ IsValidJson::class, - EnsureFrontendRequestsAreStateful::class, + EnsureStatefulRequests::class, 'auth:sanctum', RequireTwoFactorAuthentication::class, AuthenticateIPAccess::class, diff --git a/app/Http/Middleware/EnsureStatefulRequests.php b/app/Http/Middleware/EnsureStatefulRequests.php new file mode 100644 index 0000000000..2671763c99 --- /dev/null +++ b/app/Http/Middleware/EnsureStatefulRequests.php @@ -0,0 +1,57 @@ +configureSecureCookieSessions(); + + return (new Pipeline(app())) + ->send($request) + ->through($this->isStateful($request) ? $this->statefulMiddleware() : []) + ->then(fn ($request) => $next($request)); + } + + /** + * Determines if a request is stateful or not. This is determined using the default + * Sanctum "fromFrontend" helper method. However, we also check if the request includes + * a cookie value for the Pterodactyl session. If so, we assume this is a stateful + * request. + * + * We don't want to support API usage using the cookies, except for requests stemming + * from the front-end we control. + */ + protected function isStateful(Request $request): bool + { + return static::fromFrontend($request) || $request->hasCookie(config('session.cookie')); + } + + /** + * Returns the middleware to be applied to a stateful request to the API. + */ + protected function statefulMiddleware(): array + { + return [ + function ($request, $next) { + $request->attributes->set('sanctum', true); + + return $next($request); + }, + config('sanctum.middleware.encrypt_cookies', EncryptCookies::class), + AddQueuedCookiesToResponse::class, + StartSession::class, + config('sanctum.middleware.verify_csrf_token', VerifyCsrfToken::class), + ]; + } +} From 56f15c15a13f7acffb3617b594bb127d7adafa86 Mon Sep 17 00:00:00 2001 From: DaneEveritt Date: Sun, 22 May 2022 16:54:07 -0400 Subject: [PATCH 049/458] We can make this middleware significantly simpler --- .../Middleware/EnsureStatefulRequests.php | 41 +++---------------- 1 file changed, 5 insertions(+), 36 deletions(-) diff --git a/app/Http/Middleware/EnsureStatefulRequests.php b/app/Http/Middleware/EnsureStatefulRequests.php index 2671763c99..db6e19ae91 100644 --- a/app/Http/Middleware/EnsureStatefulRequests.php +++ b/app/Http/Middleware/EnsureStatefulRequests.php @@ -2,27 +2,10 @@ namespace Pterodactyl\Http\Middleware; -use Illuminate\Http\Request; -use Illuminate\Routing\Pipeline; -use Illuminate\Session\Middleware\StartSession; -use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse; use Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful; class EnsureStatefulRequests extends EnsureFrontendRequestsAreStateful { - /** - * {@inheritDoc} - */ - public function handle($request, $next) - { - $this->configureSecureCookieSessions(); - - return (new Pipeline(app())) - ->send($request) - ->through($this->isStateful($request) ? $this->statefulMiddleware() : []) - ->then(fn ($request) => $next($request)); - } - /** * Determines if a request is stateful or not. This is determined using the default * Sanctum "fromFrontend" helper method. However, we also check if the request includes @@ -32,26 +15,12 @@ public function handle($request, $next) * We don't want to support API usage using the cookies, except for requests stemming * from the front-end we control. */ - protected function isStateful(Request $request): bool - { - return static::fromFrontend($request) || $request->hasCookie(config('session.cookie')); - } - - /** - * Returns the middleware to be applied to a stateful request to the API. - */ - protected function statefulMiddleware(): array + public static function fromFrontend($request) { - return [ - function ($request, $next) { - $request->attributes->set('sanctum', true); + if (parent::fromFrontend($request)) { + return true; + } - return $next($request); - }, - config('sanctum.middleware.encrypt_cookies', EncryptCookies::class), - AddQueuedCookiesToResponse::class, - StartSession::class, - config('sanctum.middleware.verify_csrf_token', VerifyCsrfToken::class), - ]; + return $request->hasCookie(config('session.cookie')); } } From be88e4e8933fdf1805192c5c8c8e4c2e606416ef Mon Sep 17 00:00:00 2001 From: DaneEveritt Date: Sun, 22 May 2022 17:01:39 -0400 Subject: [PATCH 050/458] Ignore migrations, pass credentials --- app/Providers/AuthServiceProvider.php | 8 +++++--- resources/scripts/api/http.ts | 1 + 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/app/Providers/AuthServiceProvider.php b/app/Providers/AuthServiceProvider.php index 76ac26f376..cc06f944f9 100644 --- a/app/Providers/AuthServiceProvider.php +++ b/app/Providers/AuthServiceProvider.php @@ -19,13 +19,15 @@ class AuthServiceProvider extends ServiceProvider Server::class => ServerPolicy::class, ]; - /** - * Register any application authentication / authorization services. - */ public function boot() { Sanctum::usePersonalAccessTokenModel(ApiKey::class); $this->registerPolicies(); } + + public function register() + { + Sanctum::ignoreMigrations(); + } } diff --git a/resources/scripts/api/http.ts b/resources/scripts/api/http.ts index 9f810b648b..cf15142742 100644 --- a/resources/scripts/api/http.ts +++ b/resources/scripts/api/http.ts @@ -2,6 +2,7 @@ import axios, { AxiosInstance } from 'axios'; import { store } from '@/state'; const http: AxiosInstance = axios.create({ + withCredentials: true, timeout: 20000, headers: { 'X-Requested-With': 'XMLHttpRequest', From 4d3362b24fc17eb2492fcb3b1cecbefc027b3fa6 Mon Sep 17 00:00:00 2001 From: DaneEveritt Date: Sun, 22 May 2022 17:23:48 -0400 Subject: [PATCH 051/458] Perform a bit of code cleanup --- app/Http/Controllers/Auth/LoginController.php | 2 + app/Http/Kernel.php | 4 +- app/Http/Middleware/Authenticate.php | 26 ------------- app/Providers/RouteServiceProvider.php | 5 ++- resources/scripts/api/http.ts | 12 ------ .../Unit/Http/Middleware/AuthenticateTest.php | 39 ------------------- 6 files changed, 6 insertions(+), 82 deletions(-) delete mode 100644 app/Http/Middleware/Authenticate.php delete mode 100644 tests/Unit/Http/Middleware/AuthenticateTest.php diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php index 3c477c5568..69734ab8b7 100644 --- a/app/Http/Controllers/Auth/LoginController.php +++ b/app/Http/Controllers/Auth/LoginController.php @@ -86,6 +86,8 @@ public function login(Request $request): JsonResponse $this->auth->guard()->login($user, true); + $request->session()->regenerate(); + return $this->sendLoginResponse($user, $request); } } diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 622a8324da..4c98819492 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -11,7 +11,6 @@ use Pterodactyl\Http\Middleware\Api\IsValidJson; use Pterodactyl\Http\Middleware\VerifyCsrfToken; use Pterodactyl\Http\Middleware\VerifyReCaptcha; -use Pterodactyl\Http\Middleware\AdminAuthenticate; use Illuminate\Routing\Middleware\ThrottleRequests; use Pterodactyl\Http\Middleware\LanguageMiddleware; use Illuminate\Foundation\Http\Kernel as HttpKernel; @@ -65,9 +64,9 @@ class Kernel extends HttpKernel RequireTwoFactorAuthentication::class, ], 'api' => [ - IsValidJson::class, EnsureStatefulRequests::class, 'auth:sanctum', + IsValidJson::class, RequireTwoFactorAuthentication::class, AuthenticateIPAccess::class, ], @@ -93,7 +92,6 @@ class Kernel extends HttpKernel 'auth' => Authenticate::class, 'auth.basic' => AuthenticateWithBasicAuth::class, 'guest' => RedirectIfAuthenticated::class, - 'admin' => AdminAuthenticate::class, 'csrf' => VerifyCsrfToken::class, 'throttle' => ThrottleRequests::class, 'can' => Authorize::class, diff --git a/app/Http/Middleware/Authenticate.php b/app/Http/Middleware/Authenticate.php deleted file mode 100644 index f95180b20c..0000000000 --- a/app/Http/Middleware/Authenticate.php +++ /dev/null @@ -1,26 +0,0 @@ -user()) { - throw new AuthenticationException(); - } - - return $next($request); - } -} diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index 428b9512a5..2177eb9166 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -8,6 +8,7 @@ use Illuminate\Cache\RateLimiting\Limit; use Illuminate\Support\Facades\RateLimiter; use Pterodactyl\Http\Middleware\TrimStrings; +use Pterodactyl\Http\Middleware\AdminAuthenticate; use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider; class RouteServiceProvider extends ServiceProvider @@ -33,10 +34,10 @@ public function boot() Route::model('database', Database::class); $this->routes(function () { - Route::middleware(['web', 'csrf'])->group(function () { + Route::middleware('web')->group(function () { Route::middleware('auth')->group(base_path('routes/base.php')); Route::middleware('guest')->prefix('/auth')->group(base_path('routes/auth.php')); - Route::middleware(['auth', 'admin'])->prefix('/admin')->group(base_path('routes/admin.php')); + Route::middleware(['auth', AdminAuthenticate::class])->prefix('/admin')->group(base_path('routes/admin.php')); }); Route::middleware('api')->group(function () { diff --git a/resources/scripts/api/http.ts b/resources/scripts/api/http.ts index cf15142742..78aa393ee0 100644 --- a/resources/scripts/api/http.ts +++ b/resources/scripts/api/http.ts @@ -11,18 +11,6 @@ const http: AxiosInstance = axios.create({ }, }); -http.interceptors.request.use(req => { - const cookies = document.cookie.split(';').reduce((obj, val) => { - const [ key, value ] = val.trim().split('=').map(decodeURIComponent); - - return { ...obj, [key]: value }; - }, {} as Record); - - req.headers['X-XSRF-TOKEN'] = cookies['XSRF-TOKEN'] || 'nil'; - - return req; -}); - http.interceptors.request.use(req => { if (!req.url?.endsWith('/resources')) { store.getActions().progress.startContinuous(); diff --git a/tests/Unit/Http/Middleware/AuthenticateTest.php b/tests/Unit/Http/Middleware/AuthenticateTest.php deleted file mode 100644 index 828afc1222..0000000000 --- a/tests/Unit/Http/Middleware/AuthenticateTest.php +++ /dev/null @@ -1,39 +0,0 @@ -request->shouldReceive('user')->withNoArgs()->once()->andReturn(true); - - $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); - } - - /** - * Test that a logged out user results in an exception. - */ - public function testLoggedOutUser() - { - $this->expectException(AuthenticationException::class); - - $this->request->shouldReceive('user')->withNoArgs()->once()->andReturnNull(); - - $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); - } - - /** - * Return an instance of the middleware using mocked dependencies. - */ - private function getMiddleware(): Authenticate - { - return new Authenticate(); - } -} From 3ae70efc14d47700721406aad1a0fdf6891bff3b Mon Sep 17 00:00:00 2001 From: DaneEveritt Date: Sun, 22 May 2022 17:26:32 -0400 Subject: [PATCH 052/458] Use existing method to handle the login --- app/Http/Controllers/Auth/LoginController.php | 34 ++++++++----------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php index 69734ab8b7..b3ee4b2a42 100644 --- a/app/Http/Controllers/Auth/LoginController.php +++ b/app/Http/Controllers/Auth/LoginController.php @@ -67,27 +67,21 @@ public function login(Request $request): JsonResponse $this->sendFailedLoginResponse($request, $user); } - if ($user->use_totp) { - $token = Str::random(64); - - $request->session()->put('auth_confirmation_token', [ - 'user_id' => $user->id, - 'token_value' => $token, - 'expires_at' => CarbonImmutable::now()->addMinutes(5), - ]); - - return new JsonResponse([ - 'data' => [ - 'complete' => false, - 'confirmation_token' => $token, - ], - ]); + if (!$user->use_totp) { + return $this->sendLoginResponse($user, $request); } - $this->auth->guard()->login($user, true); - - $request->session()->regenerate(); - - return $this->sendLoginResponse($user, $request); + $request->session()->put('auth_confirmation_token', [ + 'user_id' => $user->id, + 'token_value' => $token = Str::random(64), + 'expires_at' => CarbonImmutable::now()->addMinutes(5), + ]); + + return new JsonResponse([ + 'data' => [ + 'complete' => false, + 'confirmation_token' => $token, + ], + ]); } } From dca53611ff86999144ee177548861ce30d92c854 Mon Sep 17 00:00:00 2001 From: DaneEveritt Date: Sun, 22 May 2022 18:16:47 -0400 Subject: [PATCH 053/458] Ensure we don't cause a mess with the auth providers --- app/Http/Kernel.php | 13 ++- app/Providers/RouteServiceProvider.php | 12 +- composer.json | 1 + composer.lock | 152 ++++++++++++++++++++++++- config/cors.php | 59 ++++++++++ resources/scripts/api/auth/login.ts | 11 +- 6 files changed, 233 insertions(+), 15 deletions(-) create mode 100644 config/cors.php diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 4c98819492..f2c113b8cc 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -2,6 +2,7 @@ namespace Pterodactyl\Http; +use Fruitcake\Cors\HandleCors; use Illuminate\Auth\Middleware\Authorize; use Illuminate\Auth\Middleware\Authenticate; use Illuminate\Http\Middleware\TrustProxies; @@ -26,9 +27,9 @@ use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse; use Pterodactyl\Http\Middleware\Api\Daemon\DaemonAuthenticate; use Pterodactyl\Http\Middleware\RequireTwoFactorAuthentication; -use Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode; use Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull; use Pterodactyl\Http\Middleware\Api\Client\SubstituteClientBindings; +use Illuminate\Foundation\Http\Middleware\PreventRequestsDuringMaintenance; use Pterodactyl\Http\Middleware\Api\Application\AuthenticateApplicationUser; class Kernel extends HttpKernel @@ -39,12 +40,12 @@ class Kernel extends HttpKernel * @var array */ protected $middleware = [ - CheckForMaintenanceMode::class, - EncryptCookies::class, + TrustProxies::class, + HandleCors::class, + PreventRequestsDuringMaintenance::class, ValidatePostSize::class, TrimStrings::class, ConvertEmptyStringsToNull::class, - TrustProxies::class, ]; /** @@ -54,14 +55,13 @@ class Kernel extends HttpKernel */ protected $middlewareGroups = [ 'web' => [ + EncryptCookies::class, AddQueuedCookiesToResponse::class, StartSession::class, - AuthenticateSession::class, ShareErrorsFromSession::class, VerifyCsrfToken::class, SubstituteBindings::class, LanguageMiddleware::class, - RequireTwoFactorAuthentication::class, ], 'api' => [ EnsureStatefulRequests::class, @@ -91,6 +91,7 @@ class Kernel extends HttpKernel protected $routeMiddleware = [ 'auth' => Authenticate::class, 'auth.basic' => AuthenticateWithBasicAuth::class, + 'auth.session' => AuthenticateSession::class, 'guest' => RedirectIfAuthenticated::class, 'csrf' => VerifyCsrfToken::class, 'throttle' => ThrottleRequests::class, diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index 2177eb9166..f5ac5565d5 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -9,6 +9,7 @@ use Illuminate\Support\Facades\RateLimiter; use Pterodactyl\Http\Middleware\TrimStrings; use Pterodactyl\Http\Middleware\AdminAuthenticate; +use Pterodactyl\Http\Middleware\RequireTwoFactorAuthentication; use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider; class RouteServiceProvider extends ServiceProvider @@ -35,12 +36,17 @@ public function boot() $this->routes(function () { Route::middleware('web')->group(function () { - Route::middleware('auth')->group(base_path('routes/base.php')); + Route::middleware(['auth.session', RequireTwoFactorAuthentication::class]) + ->group(base_path('routes/base.php')); + + Route::middleware(['auth.session', RequireTwoFactorAuthentication::class, AdminAuthenticate::class]) + ->prefix('/admin') + ->group(base_path('routes/admin.php')); + Route::middleware('guest')->prefix('/auth')->group(base_path('routes/auth.php')); - Route::middleware(['auth', AdminAuthenticate::class])->prefix('/admin')->group(base_path('routes/admin.php')); }); - Route::middleware('api')->group(function () { + Route::middleware(['api', RequireTwoFactorAuthentication::class])->group(function () { Route::middleware(['application-api', 'throttle:api.application']) ->prefix('/api/application') ->scopeBindings() diff --git a/composer.json b/composer.json index eb553e6a71..e8fe363707 100644 --- a/composer.json +++ b/composer.json @@ -19,6 +19,7 @@ "ext-zip": "*", "aws/aws-sdk-php": "^3.171", "doctrine/dbal": "~2.13.9", + "fruitcake/laravel-cors": "~3.0.0", "guzzlehttp/guzzle": "~7.4.2", "hashids/hashids": "~4.1.0", "laracasts/utilities": "~3.2.1", diff --git a/composer.lock b/composer.lock index 534c1958a8..b13af61adb 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "0368e946c40456bcd1fb007bfc3e7bf0", + "content-hash": "3bd4e0acecbf871892a813141facfaea", "packages": [ { "name": "aws/aws-crt-php", @@ -923,6 +923,156 @@ ], "time": "2020-12-29T14:50:06+00:00" }, + { + "name": "fruitcake/laravel-cors", + "version": "v3.0.0", + "source": { + "type": "git", + "url": "https://github.com/fruitcake/laravel-cors.git", + "reference": "7c036ec08972d8d5d9db637e772af6887828faf5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/fruitcake/laravel-cors/zipball/7c036ec08972d8d5d9db637e772af6887828faf5", + "reference": "7c036ec08972d8d5d9db637e772af6887828faf5", + "shasum": "" + }, + "require": { + "fruitcake/php-cors": "^1.2", + "illuminate/contracts": "^6|^7|^8|^9", + "illuminate/support": "^6|^7|^8|^9", + "php": "^7.4|^8.0" + }, + "require-dev": { + "laravel/framework": "^6|^7.24|^8", + "orchestra/testbench-dusk": "^4|^5|^6|^7", + "phpunit/phpunit": "^9", + "squizlabs/php_codesniffer": "^3.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + }, + "laravel": { + "providers": [ + "Fruitcake\\Cors\\CorsServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Fruitcake\\Cors\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fruitcake", + "homepage": "https://fruitcake.nl" + }, + { + "name": "Barry vd. Heuvel", + "email": "barryvdh@gmail.com" + } + ], + "description": "Adds CORS (Cross-Origin Resource Sharing) headers support in your Laravel application", + "keywords": [ + "api", + "cors", + "crossdomain", + "laravel" + ], + "support": { + "issues": "https://github.com/fruitcake/laravel-cors/issues", + "source": "https://github.com/fruitcake/laravel-cors/tree/v3.0.0" + }, + "funding": [ + { + "url": "https://fruitcake.nl", + "type": "custom" + }, + { + "url": "https://github.com/barryvdh", + "type": "github" + } + ], + "time": "2022-02-23T14:53:22+00:00" + }, + { + "name": "fruitcake/php-cors", + "version": "v1.2.0", + "source": { + "type": "git", + "url": "https://github.com/fruitcake/php-cors.git", + "reference": "58571acbaa5f9f462c9c77e911700ac66f446d4e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/fruitcake/php-cors/zipball/58571acbaa5f9f462c9c77e911700ac66f446d4e", + "reference": "58571acbaa5f9f462c9c77e911700ac66f446d4e", + "shasum": "" + }, + "require": { + "php": "^7.4|^8.0", + "symfony/http-foundation": "^4.4|^5.4|^6" + }, + "require-dev": { + "phpstan/phpstan": "^1.4", + "phpunit/phpunit": "^9", + "squizlabs/php_codesniffer": "^3.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "Fruitcake\\Cors\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fruitcake", + "homepage": "https://fruitcake.nl" + }, + { + "name": "Barryvdh", + "email": "barryvdh@gmail.com" + } + ], + "description": "Cross-origin resource sharing library for the Symfony HttpFoundation", + "homepage": "https://github.com/fruitcake/php-cors", + "keywords": [ + "cors", + "laravel", + "symfony" + ], + "support": { + "issues": "https://github.com/fruitcake/php-cors/issues", + "source": "https://github.com/fruitcake/php-cors/tree/v1.2.0" + }, + "funding": [ + { + "url": "https://fruitcake.nl", + "type": "custom" + }, + { + "url": "https://github.com/barryvdh", + "type": "github" + } + ], + "time": "2022-02-20T15:07:15+00:00" + }, { "name": "graham-campbell/result-type", "version": "v1.0.4", diff --git a/config/cors.php b/config/cors.php new file mode 100644 index 0000000000..a96bb270cf --- /dev/null +++ b/config/cors.php @@ -0,0 +1,59 @@ + ['/api/client', '/api/application', '/api/client/*', '/api/application/*'], + + /* + * Matches the request method. `['*']` allows all methods. + */ + 'allowed_methods' => ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD'], + + /* + * Matches the request origin. `['*']` allows all origins. Wildcards can be used, eg `*.mydomain.com` + */ + 'allowed_origins' => explode(',', env('APP_CORS_ALLOWED_ORIGINS') ?? ''), + + /* + * Patterns that can be used with `preg_match` to match the origin. + */ + 'allowed_origins_patterns' => [], + + /* + * Sets the Access-Control-Allow-Headers response header. `['*']` allows all headers. + */ + 'allowed_headers' => ['*'], + + /* + * Sets the Access-Control-Expose-Headers response header with these headers. + */ + 'exposed_headers' => [], + + /* + * Sets the Access-Control-Max-Age response header when > 0. + */ + 'max_age' => 0, + + /* + * Sets the Access-Control-Allow-Credentials header. + */ + 'supports_credentials' => true, +]; diff --git a/resources/scripts/api/auth/login.ts b/resources/scripts/api/auth/login.ts index af2f5faa38..379b6590ee 100644 --- a/resources/scripts/api/auth/login.ts +++ b/resources/scripts/api/auth/login.ts @@ -14,11 +14,12 @@ export interface LoginData { export default ({ username, password, recaptchaData }: LoginData): Promise => { return new Promise((resolve, reject) => { - http.post('/auth/login', { - user: username, - password, - 'g-recaptcha-response': recaptchaData, - }) + http.get('/sanctum/csrf-cookie') + .then(() => http.post('/auth/login', { + user: username, + password, + 'g-recaptcha-response': recaptchaData, + })) .then(response => { if (!(response.data instanceof Object)) { return reject(new Error('An error occurred while processing the login request.')); From 3f99b00cf792bae5ff70fa1ffe795ff3cf62f422 Mon Sep 17 00:00:00 2001 From: DaneEveritt Date: Sun, 22 May 2022 18:21:38 -0400 Subject: [PATCH 054/458] Fix display exception handling --- app/Exceptions/DisplayException.php | 17 +++++++++++++++-- app/Exceptions/Handler.php | 9 +++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/app/Exceptions/DisplayException.php b/app/Exceptions/DisplayException.php index 636b9dfc94..c7b3b2edb7 100644 --- a/app/Exceptions/DisplayException.php +++ b/app/Exceptions/DisplayException.php @@ -8,8 +8,9 @@ use Illuminate\Http\Response; use Illuminate\Container\Container; use Prologue\Alerts\AlertsMessageBag; +use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; -class DisplayException extends PterodactylException +class DisplayException extends PterodactylException implements HttpExceptionInterface { public const LEVEL_DEBUG = 'debug'; public const LEVEL_INFO = 'info'; @@ -51,6 +52,14 @@ public function getStatusCode() return Response::HTTP_BAD_REQUEST; } + /** + * @return array + */ + public function getHeaders() + { + return []; + } + /** * Render the exception to the user by adding a flashed message to the session * and then redirecting them back to the page that they came from. If the @@ -58,10 +67,14 @@ public function getStatusCode() * * @param \Illuminate\Http\Request $request * - * @return \Illuminate\Http\RedirectResponse + * @return \Illuminate\Http\JsonResponse|\Illuminate\Http\RedirectResponse */ public function render($request) { + if ($request->expectsJson()) { + return response()->json(Handler::toArray($this), $this->getStatusCode(), $this->getHeaders()); + } + app(AlertsMessageBag::class)->danger($this->getMessage())->flash(); return redirect()->back()->withInput(); diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index 2155f6c3db..60ee4d542c 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -284,4 +284,13 @@ protected function extractPrevious(Throwable $e): array return $previous; } + + /** + * Helper method to allow reaching into the handler to convert an exception + * into the expected array response type. + */ + public static function toArray(Throwable $e): array + { + return (new self(app()))->convertExceptionToArray($e); + } } From 8605d175d637d58ca4f060b84beada3b1c305c1c Mon Sep 17 00:00:00 2001 From: DaneEveritt Date: Sun, 22 May 2022 18:56:22 -0400 Subject: [PATCH 055/458] Ensure admin endpoints continue to work --- routes/admin.php | 130 +++++++++++++++++++++++------------------------ 1 file changed, 65 insertions(+), 65 deletions(-) diff --git a/routes/admin.php b/routes/admin.php index d89ae51db0..055c84a4a1 100644 --- a/routes/admin.php +++ b/routes/admin.php @@ -33,10 +33,10 @@ */ Route::group(['prefix' => 'locations'], function () { Route::get('/', [Admin\LocationController::class, 'index'])->name('admin.locations'); - Route::get('/view/{location}', [Admin\LocationController::class, 'view'])->name('admin.locations.view'); + Route::get('/view/{location:id}', [Admin\LocationController::class, 'view'])->name('admin.locations.view'); Route::post('/', [Admin\LocationController::class, 'create']); - Route::patch('/view/{location}', [Admin\LocationController::class, 'update']); + Route::patch('/view/{location:id}', [Admin\LocationController::class, 'update']); }); /* @@ -49,11 +49,11 @@ */ Route::group(['prefix' => 'databases'], function () { Route::get('/', [Admin\DatabaseController::class, 'index'])->name('admin.databases'); - Route::get('/view/{host}', [Admin\DatabaseController::class, 'view'])->name('admin.databases.view'); + Route::get('/view/{host:id}', [Admin\DatabaseController::class, 'view'])->name('admin.databases.view'); Route::post('/', [Admin\DatabaseController::class, 'create']); - Route::patch('/view/{host}', [Admin\DatabaseController::class, 'update']); - Route::delete('/view/{host}', [Admin\DatabaseController::class, 'delete']); + Route::patch('/view/{host:id}', [Admin\DatabaseController::class, 'update']); + Route::delete('/view/{host:id}', [Admin\DatabaseController::class, 'delete']); }); /* @@ -88,12 +88,12 @@ Route::get('/', [Admin\UserController::class, 'index'])->name('admin.users'); Route::get('/accounts.json', [Admin\UserController::class, 'json'])->name('admin.users.json'); Route::get('/new', [Admin\UserController::class, 'create'])->name('admin.users.new'); - Route::get('/view/{user}', [Admin\UserController::class, 'view'])->name('admin.users.view'); + Route::get('/view/{user:id}', [Admin\UserController::class, 'view'])->name('admin.users.view'); Route::post('/new', [Admin\UserController::class, 'store']); - Route::patch('/view/{user}', [Admin\UserController::class, 'update']); - Route::delete('/view/{user}', [Admin\UserController::class, 'delete']); + Route::patch('/view/{user:id}', [Admin\UserController::class, 'update']); + Route::delete('/view/{user:id}', [Admin\UserController::class, 'delete']); }); /* @@ -107,35 +107,35 @@ Route::group(['prefix' => 'servers'], function () { Route::get('/', [Admin\Servers\ServerController::class, 'index'])->name('admin.servers'); Route::get('/new', [Admin\Servers\CreateServerController::class, 'index'])->name('admin.servers.new'); - Route::get('/view/{server}', [Admin\Servers\ServerViewController::class, 'index'])->name('admin.servers.view'); + Route::get('/view/{server:id}', [Admin\Servers\ServerViewController::class, 'index'])->name('admin.servers.view'); Route::group(['middleware' => [ServerInstalled::class]], function () { - Route::get('/view/{server}/details', [Admin\Servers\ServerViewController::class, 'details'])->name('admin.servers.view.details'); - Route::get('/view/{server}/build', [Admin\Servers\ServerViewController::class, 'build'])->name('admin.servers.view.build'); - Route::get('/view/{server}/startup', [Admin\Servers\ServerViewController::class, 'startup'])->name('admin.servers.view.startup'); - Route::get('/view/{server}/database', [Admin\Servers\ServerViewController::class, 'database'])->name('admin.servers.view.database'); - Route::get('/view/{server}/mounts', [Admin\Servers\ServerViewController::class, 'mounts'])->name('admin.servers.view.mounts'); + Route::get('/view/{server:id}/details', [Admin\Servers\ServerViewController::class, 'details'])->name('admin.servers.view.details'); + Route::get('/view/{server:id}/build', [Admin\Servers\ServerViewController::class, 'build'])->name('admin.servers.view.build'); + Route::get('/view/{server:id}/startup', [Admin\Servers\ServerViewController::class, 'startup'])->name('admin.servers.view.startup'); + Route::get('/view/{server:id}/database', [Admin\Servers\ServerViewController::class, 'database'])->name('admin.servers.view.database'); + Route::get('/view/{server:id}/mounts', [Admin\Servers\ServerViewController::class, 'mounts'])->name('admin.servers.view.mounts'); }); - Route::get('/view/{server}/manage', [Admin\Servers\ServerViewController::class, 'manage'])->name('admin.servers.view.manage'); - Route::get('/view/{server}/delete', [Admin\Servers\ServerViewController::class, 'delete'])->name('admin.servers.view.delete'); + Route::get('/view/{server:id}/manage', [Admin\Servers\ServerViewController::class, 'manage'])->name('admin.servers.view.manage'); + Route::get('/view/{server:id}/delete', [Admin\Servers\ServerViewController::class, 'delete'])->name('admin.servers.view.delete'); Route::post('/new', [Admin\Servers\CreateServerController::class, 'store']); - Route::post('/view/{server}/build', [Admin\ServersController::class, 'updateBuild']); - Route::post('/view/{server}/startup', [Admin\ServersController::class, 'saveStartup']); - Route::post('/view/{server}/database', [Admin\ServersController::class, 'newDatabase']); - Route::post('/view/{server}/mounts/{mount}', [Admin\ServersController::class, 'addMount'])->name('admin.servers.view.mounts.toggle'); - Route::post('/view/{server}/manage/toggle', [Admin\ServersController::class, 'toggleInstall'])->name('admin.servers.view.manage.toggle'); - Route::post('/view/{server}/manage/suspension', [Admin\ServersController::class, 'manageSuspension'])->name('admin.servers.view.manage.suspension'); - Route::post('/view/{server}/manage/reinstall', [Admin\ServersController::class, 'reinstallServer'])->name('admin.servers.view.manage.reinstall'); - Route::post('/view/{server}/manage/transfer', [Admin\Servers\ServerTransferController::class, 'transfer'])->name('admin.servers.view.manage.transfer'); - Route::post('/view/{server}/delete', [Admin\ServersController::class, 'delete']); - - Route::patch('/view/{server}/details', [Admin\ServersController::class, 'setDetails']); - Route::patch('/view/{server}/database', [Admin\ServersController::class, 'resetDatabasePassword']); - - Route::delete('/view/{server}/database/{database}/delete', [Admin\ServersController::class, 'deleteDatabase'])->name('admin.servers.view.database.delete'); - Route::delete('/view/{server}/mounts/{mount}', [Admin\ServersController::class, 'deleteMount']); + Route::post('/view/{server:id}/build', [Admin\ServersController::class, 'updateBuild']); + Route::post('/view/{server:id}/startup', [Admin\ServersController::class, 'saveStartup']); + Route::post('/view/{server:id}/database', [Admin\ServersController::class, 'newDatabase']); + Route::post('/view/{server:id}/mounts/{mount:id}', [Admin\ServersController::class, 'addMount'])->name('admin.servers.view.mounts.toggle'); + Route::post('/view/{server:id}/manage/toggle', [Admin\ServersController::class, 'toggleInstall'])->name('admin.servers.view.manage.toggle'); + Route::post('/view/{server:id}/manage/suspension', [Admin\ServersController::class, 'manageSuspension'])->name('admin.servers.view.manage.suspension'); + Route::post('/view/{server:id}/manage/reinstall', [Admin\ServersController::class, 'reinstallServer'])->name('admin.servers.view.manage.reinstall'); + Route::post('/view/{server:id}/manage/transfer', [Admin\Servers\ServerTransferController::class, 'transfer'])->name('admin.servers.view.manage.transfer'); + Route::post('/view/{server:id}/delete', [Admin\ServersController::class, 'delete']); + + Route::patch('/view/{server:id}/details', [Admin\ServersController::class, 'setDetails']); + Route::patch('/view/{server:id}/database', [Admin\ServersController::class, 'resetDatabasePassword']); + + Route::delete('/view/{server:id}/database/{database:id}/delete', [Admin\ServersController::class, 'deleteDatabase'])->name('admin.servers.view.database.delete'); + Route::delete('/view/{server:id}/mounts/{mount:id}', [Admin\ServersController::class, 'deleteMount']); }); /* @@ -149,24 +149,24 @@ Route::group(['prefix' => 'nodes'], function () { Route::get('/', [Admin\Nodes\NodeController::class, 'index'])->name('admin.nodes'); Route::get('/new', [Admin\NodesController::class, 'create'])->name('admin.nodes.new'); - Route::get('/view/{node}', [Admin\Nodes\NodeViewController::class, 'index'])->name('admin.nodes.view'); - Route::get('/view/{node}/settings', [Admin\Nodes\NodeViewController::class, 'settings'])->name('admin.nodes.view.settings'); - Route::get('/view/{node}/configuration', [Admin\Nodes\NodeViewController::class, 'configuration'])->name('admin.nodes.view.configuration'); - Route::get('/view/{node}/allocation', [Admin\Nodes\NodeViewController::class, 'allocations'])->name('admin.nodes.view.allocation'); - Route::get('/view/{node}/servers', [Admin\Nodes\NodeViewController::class, 'servers'])->name('admin.nodes.view.servers'); - Route::get('/view/{node}/system-information', Admin\Nodes\SystemInformationController::class); + Route::get('/view/{node:id}', [Admin\Nodes\NodeViewController::class, 'index'])->name('admin.nodes.view'); + Route::get('/view/{node:id}/settings', [Admin\Nodes\NodeViewController::class, 'settings'])->name('admin.nodes.view.settings'); + Route::get('/view/{node:id}/configuration', [Admin\Nodes\NodeViewController::class, 'configuration'])->name('admin.nodes.view.configuration'); + Route::get('/view/{node:id}/allocation', [Admin\Nodes\NodeViewController::class, 'allocations'])->name('admin.nodes.view.allocation'); + Route::get('/view/{node:id}/servers', [Admin\Nodes\NodeViewController::class, 'servers'])->name('admin.nodes.view.servers'); + Route::get('/view/{node:id}/system-information', Admin\Nodes\SystemInformationController::class); Route::post('/new', [Admin\NodesController::class, 'store']); - Route::post('/view/{node}/allocation', [Admin\NodesController::class, 'createAllocation']); - Route::post('/view/{node}/allocation/remove', [Admin\NodesController::class, 'allocationRemoveBlock'])->name('admin.nodes.view.allocation.removeBlock'); - Route::post('/view/{node}/allocation/alias', [Admin\NodesController::class, 'allocationSetAlias'])->name('admin.nodes.view.allocation.setAlias'); - Route::post('/view/{node}/settings/token', Admin\NodeAutoDeployController::class)->name('admin.nodes.view.configuration.token'); + Route::post('/view/{node:id}/allocation', [Admin\NodesController::class, 'createAllocation']); + Route::post('/view/{node:id}/allocation/remove', [Admin\NodesController::class, 'allocationRemoveBlock'])->name('admin.nodes.view.allocation.removeBlock'); + Route::post('/view/{node:id}/allocation/alias', [Admin\NodesController::class, 'allocationSetAlias'])->name('admin.nodes.view.allocation.setAlias'); + Route::post('/view/{node:id}/settings/token', Admin\NodeAutoDeployController::class)->name('admin.nodes.view.configuration.token'); - Route::patch('/view/{node}/settings', [Admin\NodesController::class, 'updateSettings']); + Route::patch('/view/{node:id}/settings', [Admin\NodesController::class, 'updateSettings']); - Route::delete('/view/{node}/delete', [Admin\NodesController::class, 'delete'])->name('admin.nodes.view.delete'); - Route::delete('/view/{node}/allocation/remove/{allocation}', [Admin\NodesController::class, 'allocationRemoveSingle'])->name('admin.nodes.view.allocation.removeSingle'); - Route::delete('/view/{node}/allocations', [Admin\NodesController::class, 'allocationRemoveMultiple'])->name('admin.nodes.view.allocation.removeMultiple'); + Route::delete('/view/{node:id}/delete', [Admin\NodesController::class, 'delete'])->name('admin.nodes.view.delete'); + Route::delete('/view/{node:id}/allocation/remove/{allocation:id}', [Admin\NodesController::class, 'allocationRemoveSingle'])->name('admin.nodes.view.allocation.removeSingle'); + Route::delete('/view/{node:id}/allocations', [Admin\NodesController::class, 'allocationRemoveMultiple'])->name('admin.nodes.view.allocation.removeMultiple'); }); /* @@ -179,16 +179,16 @@ */ Route::group(['prefix' => 'mounts'], function () { Route::get('/', [Admin\MountController::class, 'index'])->name('admin.mounts'); - Route::get('/view/{mount}', [Admin\MountController::class, 'view'])->name('admin.mounts.view'); + Route::get('/view/{mount:id}', [Admin\MountController::class, 'view'])->name('admin.mounts.view'); Route::post('/', [Admin\MountController::class, 'create']); - Route::post('/{mount}/eggs', [Admin\MountController::class, 'addEggs'])->name('admin.mounts.eggs'); - Route::post('/{mount}/nodes', [Admin\MountController::class, 'addNodes'])->name('admin.mounts.nodes'); + Route::post('/{mount:id}/eggs', [Admin\MountController::class, 'addEggs'])->name('admin.mounts.eggs'); + Route::post('/{mount:id}/nodes', [Admin\MountController::class, 'addNodes'])->name('admin.mounts.nodes'); - Route::patch('/view/{mount}', [Admin\MountController::class, 'update']); + Route::patch('/view/{mount:id}', [Admin\MountController::class, 'update']); - Route::delete('/{mount}/eggs/{egg_id}', [Admin\MountController::class, 'deleteEgg']); - Route::delete('/{mount}/nodes/{node_id}', [Admin\MountController::class, 'deleteNode']); + Route::delete('/{mount:id}/eggs/{egg_id}', [Admin\MountController::class, 'deleteEgg']); + Route::delete('/{mount:id}/nodes/{node_id}', [Admin\MountController::class, 'deleteNode']); }); /* @@ -202,26 +202,26 @@ Route::group(['prefix' => 'nests'], function () { Route::get('/', [Admin\Nests\NestController::class, 'index'])->name('admin.nests'); Route::get('/new', [Admin\Nests\NestController::class, 'create'])->name('admin.nests.new'); - Route::get('/view/{nest}', [Admin\Nests\NestController::class, 'view'])->name('admin.nests.view'); + Route::get('/view/{nest:id}', [Admin\Nests\NestController::class, 'view'])->name('admin.nests.view'); Route::get('/egg/new', [Admin\Nests\EggController::class, 'create'])->name('admin.nests.egg.new'); - Route::get('/egg/{egg}', [Admin\Nests\EggController::class, 'view'])->name('admin.nests.egg.view'); - Route::get('/egg/{egg}/export', [Admin\Nests\EggShareController::class, 'export'])->name('admin.nests.egg.export'); - Route::get('/egg/{egg}/variables', [Admin\Nests\EggVariableController::class, 'view'])->name('admin.nests.egg.variables'); - Route::get('/egg/{egg}/scripts', [Admin\Nests\EggScriptController::class, 'index'])->name('admin.nests.egg.scripts'); + Route::get('/egg/{egg:id}', [Admin\Nests\EggController::class, 'view'])->name('admin.nests.egg.view'); + Route::get('/egg/{egg:id}/export', [Admin\Nests\EggShareController::class, 'export'])->name('admin.nests.egg.export'); + Route::get('/egg/{egg:id}/variables', [Admin\Nests\EggVariableController::class, 'view'])->name('admin.nests.egg.variables'); + Route::get('/egg/{egg:id}/scripts', [Admin\Nests\EggScriptController::class, 'index'])->name('admin.nests.egg.scripts'); Route::post('/new', [Admin\Nests\NestController::class, 'store']); Route::post('/import', [Admin\Nests\EggShareController::class, 'import'])->name('admin.nests.egg.import'); Route::post('/egg/new', [Admin\Nests\EggController::class, 'store']); - Route::post('/egg/{egg}/variables', [Admin\Nests\EggVariableController::class, 'store']); + Route::post('/egg/{egg:id}/variables', [Admin\Nests\EggVariableController::class, 'store']); - Route::put('/egg/{egg}', [Admin\Nests\EggShareController::class, 'update']); + Route::put('/egg/{egg:id}', [Admin\Nests\EggShareController::class, 'update']); - Route::patch('/view/{nest}', [Admin\Nests\NestController::class, 'update']); - Route::patch('/egg/{egg}', [Admin\Nests\EggController::class, 'update']); - Route::patch('/egg/{egg}/scripts', [Admin\Nests\EggScriptController::class, 'update']); - Route::patch('/egg/{egg}/variables/{variable}', [Admin\Nests\EggVariableController::class, 'update'])->name('admin.nests.egg.variables.edit'); + Route::patch('/view/{nest:id}', [Admin\Nests\NestController::class, 'update']); + Route::patch('/egg/{egg:id}', [Admin\Nests\EggController::class, 'update']); + Route::patch('/egg/{egg:id}/scripts', [Admin\Nests\EggScriptController::class, 'update']); + Route::patch('/egg/{egg:id}/variables/{variable:id}', [Admin\Nests\EggVariableController::class, 'update'])->name('admin.nests.egg.variables.edit'); - Route::delete('/view/{nest}', [Admin\Nests\NestController::class, 'destroy']); - Route::delete('/egg/{egg}', [Admin\Nests\EggController::class, 'destroy']); - Route::delete('/egg/{egg}/variables/{variable}', [Admin\Nests\EggVariableController::class, 'destroy']); + Route::delete('/view/{nest:id}', [Admin\Nests\NestController::class, 'destroy']); + Route::delete('/egg/{egg:id}', [Admin\Nests\EggController::class, 'destroy']); + Route::delete('/egg/{egg:id}/variables/{variable:id}', [Admin\Nests\EggVariableController::class, 'destroy']); }); From b051718afe2cc69a7d395bfe27a628d7c36fa289 Mon Sep 17 00:00:00 2001 From: DaneEveritt Date: Sun, 22 May 2022 19:03:51 -0400 Subject: [PATCH 056/458] Fix up API handling logic for keys and set a prefix on all keys --- CHANGELOG.md | 8 ++++++ .../Api/Client/ApiKeyController.php | 16 +++++------ app/Http/Kernel.php | 8 +++--- .../AuthenticateApplicationUser.php | 4 ++- .../Api/Client/RequireClientApiKey.php | 27 +++++++++++++++++++ .../Api/Application/ApplicationApiRequest.php | 5 ++++ app/Models/ApiKey.php | 23 ++++++++++++---- app/Models/Traits/HasAccessTokens.php | 20 ++++++++------ app/Models/User.php | 4 +-- app/Services/Api/KeyCreationService.php | 2 +- database/Factories/ApiKeyFactory.php | 2 +- 11 files changed, 88 insertions(+), 31 deletions(-) create mode 100644 app/Http/Middleware/Api/Client/RequireClientApiKey.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 613a63dcda..eb0e8d00e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ This project follows [Semantic Versioning](http://semver.org) guidelines. should be completely seamless for most installations as the Panel is able to convert between the two. Custom solutions using these eggs should be updated to account for the new format. +This release also changes API key behavior — "client" keys belonging to admin users can now be used to access +the `/api/application` endpoints in their entirety. Existing "application" keys generated in the admin area should +be considered deprecated, but will continue to work. Application keys _will not_ work with the client API. + ### Fixed * Schedules are no longer run when a server is suspended or marked as installing. * The remote field when creating a database is no longer limited to an IP address and `%` wildcard — all expected MySQL remote host values are allowed. @@ -22,6 +26,8 @@ using these eggs should be updated to account for the new format. * Additional permissions (`CREATE TEMPORARY TABLES`, `CREATE VIEW`, `SHOW VIEW`, `EVENT`, and `TRIGGER`) are granted to users when creating new databases for servers. * development: removed Laravel Debugbar in favor of Clockwork for debugging. * The 2FA input field when logging in is now correctly identified as `one-time-password` to help browser autofill capabilities. +* Changed API authentication mechanisms to make use of Laravel Sanctum to significantly clean up our internal handling of sessions. +* API keys generated by the system now set a prefix to identify them as Pterodactyl API keys, and if they are client or application keys. This prefix looks like `ptlc_` for client keys, and `ptla_` for application keys. Existing API keys are unaffected by this change. ### Added * Added support for PHP 8.1 in addition to PHP 8.0 and 7.4. @@ -33,9 +39,11 @@ using these eggs should be updated to account for the new format. * Adds command to return the configuration for a specific node in both YAML and JSON format (`php artisan p:node:configuration`). * Adds command to return a list of all nodes available on the Panel in both table and JSON format (`php artisan p:node:list`). * Adds server network (inbound/outbound) usage graphs to the console screen. +* Adds support for configuring CORS on the API by setting the `APP_CORS_ALLOWED_ORIGINS=example.com,dashboard.example.com` environment variable. By default all instances are configured with this set to `*` which allows any origin. ### Removed * Removes Google Analytics from the front end code. +* Removes multiple middleware that were previously used for configuring API access and controlling model fetching. This has all been replaced with Laravel Sanctum and standard Laravel API tooling. This should make codebase discovery significantly more simple. ## v1.7.0 ### Fixed diff --git a/app/Http/Controllers/Api/Client/ApiKeyController.php b/app/Http/Controllers/Api/Client/ApiKeyController.php index b978cc8d4c..427c353a51 100644 --- a/app/Http/Controllers/Api/Client/ApiKeyController.php +++ b/app/Http/Controllers/Api/Client/ApiKeyController.php @@ -63,7 +63,6 @@ public function index(ClientApiRequest $request) * @return array * * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\Model\DataValidationException */ public function store(StoreApiKeyRequest $request) { @@ -71,17 +70,14 @@ public function store(StoreApiKeyRequest $request) throw new DisplayException('You have reached the account limit for number of API keys.'); } - $key = $this->keyCreationService->setKeyType(ApiKey::TYPE_ACCOUNT)->handle([ - 'user_id' => $request->user()->id, - 'memo' => $request->input('description'), - 'allowed_ips' => $request->input('allowed_ips') ?? [], - ]); + $token = $request->user()->createToken( + $request->input('description'), + $request->input('allowed_ips') + ); - return $this->fractal->item($key) + return $this->fractal->item($token->accessToken) ->transformWith($this->getTransformer(ApiKeyTransformer::class)) - ->addMeta([ - 'secret_token' => $this->encrypter->decrypt($key->token), - ]) + ->addMeta(['secret_token' => $token->plainTextToken]) ->toArray(); } diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index f2c113b8cc..bbd5d2ce2c 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -27,6 +27,7 @@ use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse; use Pterodactyl\Http\Middleware\Api\Daemon\DaemonAuthenticate; use Pterodactyl\Http\Middleware\RequireTwoFactorAuthentication; +use Pterodactyl\Http\Middleware\Api\Client\RequireClientApiKey; use Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull; use Pterodactyl\Http\Middleware\Api\Client\SubstituteClientBindings; use Illuminate\Foundation\Http\Middleware\PreventRequestsDuringMaintenance; @@ -74,9 +75,10 @@ class Kernel extends HttpKernel SubstituteBindings::class, AuthenticateApplicationUser::class, ], - // TODO: don't allow an application key to use the client API, but do allow a client - // api key to access the application API. - 'client-api' => [SubstituteClientBindings::class], + 'client-api' => [ + SubstituteClientBindings::class, + RequireClientApiKey::class, + ], 'daemon' => [ SubstituteBindings::class, DaemonAuthenticate::class, diff --git a/app/Http/Middleware/Api/Application/AuthenticateApplicationUser.php b/app/Http/Middleware/Api/Application/AuthenticateApplicationUser.php index bf9a64606a..983f4d3696 100644 --- a/app/Http/Middleware/Api/Application/AuthenticateApplicationUser.php +++ b/app/Http/Middleware/Api/Application/AuthenticateApplicationUser.php @@ -16,7 +16,9 @@ class AuthenticateApplicationUser */ public function handle(Request $request, Closure $next) { - if (is_null($request->user()) || !$request->user()->root_admin) { + /** @var \Pterodactyl\Models\User|null $user */ + $user = $request->user(); + if (!$user || !$user->root_admin) { throw new AccessDeniedHttpException('This account does not have permission to access the API.'); } diff --git a/app/Http/Middleware/Api/Client/RequireClientApiKey.php b/app/Http/Middleware/Api/Client/RequireClientApiKey.php new file mode 100644 index 0000000000..7203203c7f --- /dev/null +++ b/app/Http/Middleware/Api/Client/RequireClientApiKey.php @@ -0,0 +1,27 @@ +user()->currentAccessToken(); + + if ($token instanceof ApiKey && $token->key_type === ApiKey::TYPE_APPLICATION) { + throw new AccessDeniedHttpException('You are attempting to use an application API key on an endpoint that requires a client API key.'); + } + + return $next($request); + } +} diff --git a/app/Http/Requests/Api/Application/ApplicationApiRequest.php b/app/Http/Requests/Api/Application/ApplicationApiRequest.php index ec7643bd52..f3960b3060 100644 --- a/app/Http/Requests/Api/Application/ApplicationApiRequest.php +++ b/app/Http/Requests/Api/Application/ApplicationApiRequest.php @@ -3,6 +3,7 @@ namespace Pterodactyl\Http\Requests\Api\Application; use Webmozart\Assert\Assert; +use Pterodactyl\Models\ApiKey; use Laravel\Sanctum\TransientToken; use Illuminate\Validation\Validator; use Illuminate\Database\Eloquent\Model; @@ -45,6 +46,10 @@ public function authorize(): bool return true; } + if ($token->key_type === ApiKey::TYPE_ACCOUNT) { + return true; + } + return AdminAcl::check($token, $this->resource, $this->permission); } diff --git a/app/Models/ApiKey.php b/app/Models/ApiKey.php index cd5a0ddcae..814175a56c 100644 --- a/app/Models/ApiKey.php +++ b/app/Models/ApiKey.php @@ -3,6 +3,7 @@ namespace Pterodactyl\Models; use Illuminate\Support\Str; +use Webmozart\Assert\Assert; use Pterodactyl\Services\Acl\Api\AdminAcl; use Illuminate\Database\Eloquent\Relations\BelongsTo; @@ -194,21 +195,33 @@ public function tokenable() */ public static function findToken($token) { - $id = Str::substr($token, 0, self::IDENTIFIER_LENGTH); + $identifier = substr($token, 0, self::IDENTIFIER_LENGTH); - $model = static::where('identifier', $id)->first(); - if (!is_null($model) && decrypt($model->token) === Str::substr($token, strlen($id))) { + $model = static::where('identifier', $identifier)->first(); + if (!is_null($model) && decrypt($model->token) === substr($token, strlen($identifier))) { return $model; } return null; } + /** + * Returns the standard prefix for API keys in the system. + */ + public static function getPrefixForType(int $type): string + { + Assert::oneOf($type, [self::TYPE_ACCOUNT, self::TYPE_APPLICATION]); + + return $type === self::TYPE_ACCOUNT ? 'ptlc_' : 'ptla_'; + } + /** * Generates a new identifier for an API key. */ - public static function generateTokenIdentifier(): string + public static function generateTokenIdentifier(int $type): string { - return 'ptdl_' . Str::random(self::IDENTIFIER_LENGTH - 5); + $prefix = self::getPrefixForType($type); + + return $prefix . Str::random(self::IDENTIFIER_LENGTH - strlen($prefix)); } } diff --git a/app/Models/Traits/HasAccessTokens.php b/app/Models/Traits/HasAccessTokens.php index 2aa21cb9e9..ed042ccfa0 100644 --- a/app/Models/Traits/HasAccessTokens.php +++ b/app/Models/Traits/HasAccessTokens.php @@ -6,6 +6,7 @@ use Laravel\Sanctum\Sanctum; use Pterodactyl\Models\ApiKey; use Laravel\Sanctum\HasApiTokens; +use Illuminate\Database\Eloquent\Relations\HasMany; use Pterodactyl\Extensions\Laravel\Sanctum\NewAccessToken; /** @@ -13,25 +14,28 @@ */ trait HasAccessTokens { - use HasApiTokens; + use HasApiTokens { + tokens as private _tokens; + createToken as private _createToken; + } - public function tokens() + public function tokens(): HasMany { return $this->hasMany(Sanctum::$personalAccessTokenModel); } - public function createToken(string $name, array $abilities = ['*']) + public function createToken(?string $memo, ?array $ips): NewAccessToken { /** @var \Pterodactyl\Models\ApiKey $token */ - $token = $this->tokens()->create([ + $token = $this->tokens()->forceCreate([ 'user_id' => $this->id, 'key_type' => ApiKey::TYPE_ACCOUNT, - 'identifier' => ApiKey::generateTokenIdentifier(), + 'identifier' => ApiKey::generateTokenIdentifier(ApiKey::TYPE_ACCOUNT), 'token' => encrypt($plain = Str::random(ApiKey::KEY_LENGTH)), - 'memo' => $name, - 'allowed_ips' => [], + 'memo' => $memo ?? '', + 'allowed_ips' => $ips ?? [], ]); - return new NewAccessToken($token, $token->identifier . $plain); + return new NewAccessToken($token, $plain); } } diff --git a/app/Models/User.php b/app/Models/User.php index 6e676a4163..fff28b9b14 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -3,12 +3,12 @@ namespace Pterodactyl\Models; use Pterodactyl\Rules\Username; -use Laravel\Sanctum\HasApiTokens; use Illuminate\Support\Collection; use Illuminate\Validation\Rules\In; use Illuminate\Auth\Authenticatable; use Illuminate\Notifications\Notifiable; use Illuminate\Database\Eloquent\Builder; +use Pterodactyl\Models\Traits\HasAccessTokens; use Illuminate\Auth\Passwords\CanResetPassword; use Pterodactyl\Traits\Helpers\AvailableLanguages; use Illuminate\Database\Eloquent\Relations\HasMany; @@ -84,7 +84,7 @@ class User extends Model implements use Authorizable; use AvailableLanguages; use CanResetPassword; - use HasApiTokens; + use HasAccessTokens; use Notifiable; public const USER_LEVEL_USER = 0; diff --git a/app/Services/Api/KeyCreationService.php b/app/Services/Api/KeyCreationService.php index 20a11add02..29f079c81b 100644 --- a/app/Services/Api/KeyCreationService.php +++ b/app/Services/Api/KeyCreationService.php @@ -56,7 +56,7 @@ public function handle(array $data, array $permissions = []): ApiKey { $data = array_merge($data, [ 'key_type' => $this->keyType, - 'identifier' => str_random(ApiKey::IDENTIFIER_LENGTH), + 'identifier' => ApiKey::generateTokenIdentifier($this->keyType), 'token' => $this->encrypter->encrypt(str_random(ApiKey::KEY_LENGTH)), ]); diff --git a/database/Factories/ApiKeyFactory.php b/database/Factories/ApiKeyFactory.php index 77795e6075..40c78ce266 100644 --- a/database/Factories/ApiKeyFactory.php +++ b/database/Factories/ApiKeyFactory.php @@ -25,7 +25,7 @@ public function definition(): array return [ 'key_type' => ApiKey::TYPE_APPLICATION, - 'identifier' => ApiKey::generateTokenIdentifier(), + 'identifier' => ApiKey::generateTokenIdentifier(ApiKey::TYPE_APPLICATION), 'token' => $token ?: $token = encrypt(Str::random(ApiKey::KEY_LENGTH)), 'allowed_ips' => null, 'memo' => 'Test Function Key', From 3fceb588fbf39cfc5f81af17d2521d08356cae00 Mon Sep 17 00:00:00 2001 From: DaneEveritt Date: Sat, 28 May 2022 13:32:35 -0400 Subject: [PATCH 057/458] Fix router logic to account for logged out users; closes #4085 Middleware was removed from the `/` route to redirect users without authentication, so now we need to handle this on the front-end properly. --- resources/scripts/components/App.tsx | 20 ++++- .../elements/AuthenticatedRoute.tsx | 16 ++++ .../scripts/routers/AuthenticationRouter.tsx | 37 ++++++---- resources/scripts/routers/DashboardRouter.tsx | 73 ++++++++++--------- resources/scripts/routers/ServerRouter.tsx | 14 ++-- 5 files changed, 99 insertions(+), 61 deletions(-) create mode 100644 resources/scripts/components/elements/AuthenticatedRoute.tsx diff --git a/resources/scripts/components/App.tsx b/resources/scripts/components/App.tsx index 1746fb6bfc..28bbb5c6a6 100644 --- a/resources/scripts/components/App.tsx +++ b/resources/scripts/components/App.tsx @@ -13,6 +13,8 @@ import tw, { GlobalStyles as TailwindGlobalStyles } from 'twin.macro'; import GlobalStylesheet from '@/assets/css/GlobalStylesheet'; import { history } from '@/components/history'; import { setupInterceptors } from '@/api/interceptors'; +import AuthenticatedRoute from '@/components/elements/AuthenticatedRoute'; +import { ServerContext } from '@/state/server'; interface ExtendedWindow extends Window { SiteConfiguration?: SiteSettings; @@ -60,10 +62,20 @@ const App = () => {
- - - - + + + + + + + + + + + + + +
diff --git a/resources/scripts/components/elements/AuthenticatedRoute.tsx b/resources/scripts/components/elements/AuthenticatedRoute.tsx new file mode 100644 index 0000000000..9c5f2265ee --- /dev/null +++ b/resources/scripts/components/elements/AuthenticatedRoute.tsx @@ -0,0 +1,16 @@ +import React from 'react'; +import { Redirect, Route, RouteProps } from 'react-router'; +import { useStoreState } from '@/state/hooks'; + +export default ({ children, ...props }: Omit) => { + const isAuthenticated = useStoreState(state => !!state.user.data?.uuid); + + return ( + ( + isAuthenticated ? children : + )} + /> + ); +}; diff --git a/resources/scripts/routers/AuthenticationRouter.tsx b/resources/scripts/routers/AuthenticationRouter.tsx index f492631081..0e3a56b616 100644 --- a/resources/scripts/routers/AuthenticationRouter.tsx +++ b/resources/scripts/routers/AuthenticationRouter.tsx @@ -1,22 +1,29 @@ import React from 'react'; -import { Route, RouteComponentProps, Switch } from 'react-router-dom'; +import { Route, Switch, useRouteMatch } from 'react-router-dom'; import LoginContainer from '@/components/auth/LoginContainer'; import ForgotPasswordContainer from '@/components/auth/ForgotPasswordContainer'; import ResetPasswordContainer from '@/components/auth/ResetPasswordContainer'; import LoginCheckpointContainer from '@/components/auth/LoginCheckpointContainer'; import { NotFound } from '@/components/elements/ScreenBlock'; +import { useHistory, useLocation } from 'react-router'; -export default ({ location, history, match }: RouteComponentProps) => ( -
- - - - - - - - history.push('/auth/login')}/> - - -
-); +export default () => { + const history = useHistory(); + const location = useLocation(); + const { path } = useRouteMatch(); + + return ( +
+ + + + + + + + history.push('/auth/login')}/> + + +
+ ); +}; diff --git a/resources/scripts/routers/DashboardRouter.tsx b/resources/scripts/routers/DashboardRouter.tsx index 36bc5b40ee..2d907221a4 100644 --- a/resources/scripts/routers/DashboardRouter.tsx +++ b/resources/scripts/routers/DashboardRouter.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { NavLink, Route, RouteComponentProps, Switch } from 'react-router-dom'; +import { NavLink, Route, Switch } from 'react-router-dom'; import AccountOverviewContainer from '@/components/dashboard/AccountOverviewContainer'; import NavigationBar from '@/components/NavigationBar'; import DashboardContainer from '@/components/dashboard/DashboardContainer'; @@ -8,37 +8,42 @@ import { NotFound } from '@/components/elements/ScreenBlock'; import TransitionRouter from '@/TransitionRouter'; import SubNavigation from '@/components/elements/SubNavigation'; import AccountSSHContainer from '@/components/dashboard/ssh/AccountSSHContainer'; +import { useLocation } from 'react-router'; -export default ({ location }: RouteComponentProps) => ( - <> - - {location.pathname.startsWith('/account') && - -
- Settings - API Credentials - SSH Keys -
-
- } - - - - - - - - - - - - - - - - - - - - -); +export default () => { + const location = useLocation(); + + return ( + <> + + {location.pathname.startsWith('/account') && + +
+ Settings + API Credentials + SSH Keys +
+
+ } + + + + + + + + + + + + + + + + + + + + + ); +}; diff --git a/resources/scripts/routers/ServerRouter.tsx b/resources/scripts/routers/ServerRouter.tsx index 434b6192a6..f308b44b96 100644 --- a/resources/scripts/routers/ServerRouter.tsx +++ b/resources/scripts/routers/ServerRouter.tsx @@ -1,6 +1,6 @@ import TransferListener from '@/components/server/TransferListener'; import React, { useEffect, useState } from 'react'; -import { NavLink, Route, RouteComponentProps, Switch } from 'react-router-dom'; +import { NavLink, Route, Switch, useRouteMatch } from 'react-router-dom'; import NavigationBar from '@/components/NavigationBar'; import ServerConsole from '@/components/server/ServerConsole'; import TransitionRouter from '@/TransitionRouter'; @@ -31,6 +31,7 @@ import RequireServerPermission from '@/hoc/RequireServerPermission'; import ServerInstallSvg from '@/assets/images/server_installing.svg'; import ServerRestoreSvg from '@/assets/images/server_restore.svg'; import ServerErrorSvg from '@/assets/images/server_error.svg'; +import { useLocation } from 'react-router'; const ConflictStateRenderer = () => { const status = ServerContext.useStoreState(state => state.server.data?.status || null); @@ -59,7 +60,10 @@ const ConflictStateRenderer = () => { ); }; -const ServerRouter = ({ match, location }: RouteComponentProps<{ id: string }>) => { +export default () => { + const match = useRouteMatch<{ id: string }>(); + const location = useLocation(); + const rootAdmin = useStoreState(state => state.user.data!.rootAdmin); const [ error, setError ] = useState(''); @@ -194,9 +198,3 @@ const ServerRouter = ({ match, location }: RouteComponentProps<{ id: string }>) ); }; - -export default (props: RouteComponentProps) => ( - - - -); From c14c7b436e760fb74f5dcde3685cd8a8eec78d40 Mon Sep 17 00:00:00 2001 From: DaneEveritt Date: Sat, 28 May 2022 13:45:23 -0400 Subject: [PATCH 058/458] Pass along new fields to Wings instance when endpoint is used; closes #4048 --- .../Api/Client/Servers/FileController.php | 79 +++++++++++-------- .../Client/Servers/Files/PullFileRequest.php | 5 +- .../Wings/DaemonFileRepository.php | 12 ++- 3 files changed, 60 insertions(+), 36 deletions(-) diff --git a/app/Http/Controllers/Api/Client/Servers/FileController.php b/app/Http/Controllers/Api/Client/Servers/FileController.php index 5a106e037a..7e5ff45ee1 100644 --- a/app/Http/Controllers/Api/Client/Servers/FileController.php +++ b/app/Http/Controllers/Api/Client/Servers/FileController.php @@ -97,17 +97,20 @@ public function contents(GetFileContentsRequest $request, Server $server): Respo */ public function download(GetFileContentsRequest $request, Server $server) { - $token = $server->audit(AuditLog::SERVER__FILESYSTEM_DOWNLOAD, function (AuditLog $audit, Server $server) use ($request) { - $audit->metadata = ['file' => $request->get('file')]; - - return $this->jwtService - ->setExpiresAt(CarbonImmutable::now()->addMinutes(15)) - ->setClaims([ - 'file_path' => rawurldecode($request->get('file')), - 'server_uuid' => $server->uuid, - ]) - ->handle($server->node, $request->user()->id . $server->uuid); - }); + $token = $server->audit( + AuditLog::SERVER__FILESYSTEM_DOWNLOAD, + function (AuditLog $audit, Server $server) use ($request) { + $audit->metadata = ['file' => $request->get('file')]; + + return $this->jwtService + ->setExpiresAt(CarbonImmutable::now()->addMinutes(15)) + ->setClaims([ + 'file_path' => rawurldecode($request->get('file')), + 'server_uuid' => $server->uuid, + ]) + ->handle($server->node, $request->user()->id . $server->uuid); + } + ); return [ 'object' => 'signed_url', @@ -201,18 +204,21 @@ public function copy(CopyFileRequest $request, Server $server): JsonResponse */ public function compress(CompressFilesRequest $request, Server $server): array { - $file = $server->audit(AuditLog::SERVER__FILESYSTEM_COMPRESS, function (AuditLog $audit, Server $server) use ($request) { - // Allow up to five minutes for this request to process before timing out. - set_time_limit(300); - - $audit->metadata = ['root' => $request->input('root'), 'files' => $request->input('files')]; - - return $this->fileRepository->setServer($server) - ->compressFiles( - $request->input('root'), - $request->input('files') - ); - }); + $file = $server->audit( + AuditLog::SERVER__FILESYSTEM_COMPRESS, + function (AuditLog $audit, Server $server) use ($request) { + // Allow up to five minutes for this request to process before timing out. + set_time_limit(300); + + $audit->metadata = ['root' => $request->input('root'), 'files' => $request->input('files')]; + + return $this->fileRepository->setServer($server) + ->compressFiles( + $request->input('root'), + $request->input('files') + ); + } + ); return $this->fractal->item($file) ->transformWith($this->getTransformer(FileObjectTransformer::class)) @@ -224,15 +230,18 @@ public function compress(CompressFilesRequest $request, Server $server): array */ public function decompress(DecompressFilesRequest $request, Server $server): JsonResponse { - $file = $server->audit(AuditLog::SERVER__FILESYSTEM_DECOMPRESS, function (AuditLog $audit, Server $server) use ($request) { - // Allow up to five minutes for this request to process before timing out. - set_time_limit(300); + $file = $server->audit( + AuditLog::SERVER__FILESYSTEM_DECOMPRESS, + function (AuditLog $audit, Server $server) use ($request) { + // Allow up to five minutes for this request to process before timing out. + set_time_limit(300); - $audit->metadata = ['root' => $request->input('root'), 'files' => $request->input('file')]; + $audit->metadata = ['root' => $request->input('root'), 'files' => $request->input('file')]; - $this->fileRepository->setServer($server) - ->decompressFile($request->input('root'), $request->input('file')); - }); + $this->fileRepository->setServer($server) + ->decompressFile($request->input('root'), $request->input('file')); + } + ); return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT); } @@ -276,8 +285,6 @@ public function chmod(ChmodFilesRequest $request, Server $server): JsonResponse /** * Requests that a file be downloaded from a remote location by Wings. * - * @param $request - * * @throws \Throwable */ public function pull(PullFileRequest $request, Server $server): JsonResponse @@ -285,7 +292,13 @@ public function pull(PullFileRequest $request, Server $server): JsonResponse $server->audit(AuditLog::SERVER__FILESYSTEM_PULL, function (AuditLog $audit, Server $server) use ($request) { $audit->metadata = ['directory' => $request->input('directory'), 'url' => $request->input('url')]; - $this->fileRepository->setServer($server)->pull($request->input('url'), $request->input('directory')); + $this->fileRepository + ->setServer($server) + ->pull( + $request->input('url'), + $request->input('directory'), + $request->safe(['filename', 'use_header', 'foreground']) + ); }); return new JsonResponse([], Response::HTTP_NO_CONTENT); diff --git a/app/Http/Requests/Api/Client/Servers/Files/PullFileRequest.php b/app/Http/Requests/Api/Client/Servers/Files/PullFileRequest.php index ad3d578977..710ffcc7dd 100644 --- a/app/Http/Requests/Api/Client/Servers/Files/PullFileRequest.php +++ b/app/Http/Requests/Api/Client/Servers/Files/PullFileRequest.php @@ -20,7 +20,10 @@ public function rules(): array { return [ 'url' => 'required|string|url', - 'directory' => 'sometimes|nullable|string', + 'directory' => 'nullable|string', + 'filename' => 'nullable|string', + 'use_header' => 'boolean', + 'foreground' => 'boolean', ]; } } diff --git a/app/Repositories/Wings/DaemonFileRepository.php b/app/Repositories/Wings/DaemonFileRepository.php index f1acdc7de6..eb36496412 100644 --- a/app/Repositories/Wings/DaemonFileRepository.php +++ b/app/Repositories/Wings/DaemonFileRepository.php @@ -267,15 +267,23 @@ public function chmodFiles(?string $root, array $files): ResponseInterface * * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException */ - public function pull(string $url, ?string $directory): ResponseInterface + public function pull(string $url, ?string $directory, array $params = []): ResponseInterface { Assert::isInstanceOf($this->server, Server::class); + $attributes = [ + 'url' => $url, + 'root' => $directory ?? '/', + 'file_name' => $params['filename'] ?? null, + 'use_header' => $params['use_header'] ?? null, + 'foreground' => $params['foreground'] ?? null, + ]; + try { return $this->getHttpClient()->post( sprintf('/api/servers/%s/files/pull', $this->server->uuid), [ - 'json' => ['url' => $url, 'directory' => $directory ?? '/'], + 'json' => array_filter($attributes, fn ($value) => !is_null($value)), ] ); } catch (TransferException $exception) { From 5bb66a00d888409354c18c07e279d4c16ee37ec9 Mon Sep 17 00:00:00 2001 From: DaneEveritt Date: Sat, 28 May 2022 15:36:26 -0400 Subject: [PATCH 059/458] Add new activity logging code to replace audit log --- app/Facades/Activity.php | 25 +++ app/Facades/LogBatch.php | 20 ++ app/Facades/LogTarget.php | 21 +++ app/Http/Middleware/ServerActivityLogs.php | 30 +++ app/Models/ActivityLog.php | 94 ++++++++++ app/Providers/ActivityLogServiceProvider.php | 20 ++ .../Activity/AcitvityLogBatchService.php | 62 ++++++ app/Services/Activity/ActivityLogService.php | 177 ++++++++++++++++++ .../Activity/ActivityLogTargetableService.php | 47 +++++ config/app.php | 1 + ...5_28_135717_create_activity_logs_table.php | 37 ++++ 11 files changed, 534 insertions(+) create mode 100644 app/Facades/Activity.php create mode 100644 app/Facades/LogBatch.php create mode 100644 app/Facades/LogTarget.php create mode 100644 app/Http/Middleware/ServerActivityLogs.php create mode 100644 app/Models/ActivityLog.php create mode 100644 app/Providers/ActivityLogServiceProvider.php create mode 100644 app/Services/Activity/AcitvityLogBatchService.php create mode 100644 app/Services/Activity/ActivityLogService.php create mode 100644 app/Services/Activity/ActivityLogTargetableService.php create mode 100644 database/migrations/2022_05_28_135717_create_activity_logs_table.php diff --git a/app/Facades/Activity.php b/app/Facades/Activity.php new file mode 100644 index 0000000000..eb7359ece4 --- /dev/null +++ b/app/Facades/Activity.php @@ -0,0 +1,25 @@ +route()->parameter('server'); + if ($server instanceof Server) { + LogTarget::setActor($request->user()); + LogTarget::setSubject($server); + } + + return $next($request); + } +} diff --git a/app/Models/ActivityLog.php b/app/Models/ActivityLog.php new file mode 100644 index 0000000000..f58d827c65 --- /dev/null +++ b/app/Models/ActivityLog.php @@ -0,0 +1,94 @@ + 'collection', + ]; + + public static $validationRules = [ + 'event' => ['required', 'string'], + 'batch' => ['nullable', 'uuid'], + 'description' => ['nullable', 'string'], + 'properties' => ['nullable', 'array'], + ]; + + public function actor(): MorphTo + { + return $this->morphTo(); + } + + public function subject(): MorphTo + { + return $this->morphTo(); + } + + public function scopeForAction(Builder $builder, string $action): Builder + { + return $builder->where('action', $action); + } + + /** + * Scopes a query to only return results where the actor is a given model. + */ + public function scopeForActor(Builder $builder, IlluminateModel $actor): Builder + { + return $builder->whereMorphedTo('actor', $actor); + } + + /** + * Scopes a query to only return results where the subject is the given model. + */ + public function scopeForSubject(Builder $builder, IlluminateModel $subject): Builder + { + return $builder->whereMorphedTo('subject', $subject); + } +} diff --git a/app/Providers/ActivityLogServiceProvider.php b/app/Providers/ActivityLogServiceProvider.php new file mode 100644 index 0000000000..b410b4c80c --- /dev/null +++ b/app/Providers/ActivityLogServiceProvider.php @@ -0,0 +1,20 @@ +app->scoped(AcitvityLogBatchService::class); + $this->app->scoped(ActivityLogTargetableService::class); + } +} diff --git a/app/Services/Activity/AcitvityLogBatchService.php b/app/Services/Activity/AcitvityLogBatchService.php new file mode 100644 index 0000000000..af62e9e4df --- /dev/null +++ b/app/Services/Activity/AcitvityLogBatchService.php @@ -0,0 +1,62 @@ +uuid; + } + + /** + * Starts a new batch transaction. If there is already a transaction present + * this will be nested. + */ + public function start(): void + { + if ($this->transaction === 0) { + $this->uuid = Uuid::uuid4()->toString(); + } + + ++$this->transaction; + } + + /** + * Ends a batch transaction, if this is the last transaction in the stack + * the UUID will be cleared out. + */ + public function end(): void + { + $this->transaction = max(0, $this->transaction - 1); + + if ($this->transaction === 0) { + $this->uuid = null; + } + } + + /** + * Executes the logic provided within the callback in the scope of an activity + * log batch transaction. + * + * @param \Closure $callback + * @return mixed + */ + public function transaction(\Closure $callback) + { + $this->start(); + $result = $callback($this->uuid()); + $this->end(); + + return $result; + } +} diff --git a/app/Services/Activity/ActivityLogService.php b/app/Services/Activity/ActivityLogService.php new file mode 100644 index 0000000000..d34262a336 --- /dev/null +++ b/app/Services/Activity/ActivityLogService.php @@ -0,0 +1,177 @@ +manager = $manager; + $this->batch = $batch; + $this->targetable = $targetable; + $this->connection = $connection; + } + + /** + * Sets the activity logger as having been caused by an anonymous + * user type. + */ + public function anonymous(): self + { + $this->getActivity()->actor_id = null; + $this->getActivity()->actor_type = null; + $this->getActivity()->setRelation('actor', null); + + return $this; + } + + /** + * Sets the action for this activity log. + */ + public function event(string $action): self + { + $this->getActivity()->event = $action; + + return $this; + } + + /** + * Set the description for this activity. + */ + public function withDescription(?string $description): self + { + $this->getActivity()->description = $description; + + return $this; + } + + /** + * Sets the subject model instance. + */ + public function withSubject(Model $subject): self + { + $this->getActivity()->subject()->associate($subject); + + return $this; + } + + /** + * Sets the actor model instance. + */ + public function withActor(Model $actor): self + { + $this->getActivity()->actor()->associate($actor); + + return $this; + } + + /** + * Sets the custom properties for the activity log instance. + * + * @param \Illuminate\Support\Collection|array $properties + */ + public function withProperties($properties): self + { + $this->getActivity()->properties = Collection::make($properties); + + return $this; + } + + /** + * Sets a custom property on the activty log instance. + * + * @param mixed $value + */ + public function withProperty(string $key, $value): self + { + $this->getActivity()->properties = $this->getActivity()->properties->put($key, $value); + + return $this; + } + + /** + * Logs an activity log entry with the set values and then returns the + * model instance to the caller. + */ + public function log(string $description): ActivityLog + { + $this->withDescription($description); + + $activity = $this->activity; + $activity->save(); + $this->activity = null; + + return $activity; + } + + /** + * Executes the provided callback within the scope of a database transaction + * and will only save the activity log entry if everything else succesfully + * settles. + * + * @return mixed + * + * @throws \Throwable + */ + public function transaction(\Closure $callback, string $description = null) + { + if (!is_null($description)) { + $this->withDescription($description); + } + + return $this->connection->transaction(function () use ($callback) { + $response = $callback($activity = $this->getActivity()); + + $activity->save(); + $this->activity = null; + + return $response; + }); + } + + /** + * Returns the current activity log instance. + */ + protected function getActivity(): ActivityLog + { + if ($this->activity) { + return $this->activity; + } + + $this->activity = new ActivityLog([ + 'batch_uuid' => $this->batch->uuid(), + 'properties' => Collection::make([]), + ]); + + if ($subject = $this->targetable->subject()) { + $this->withSubject($subject); + } + + if ($actor = $this->targetable->actor()) { + $this->withActor($actor); + } elseif ($user = $this->manager->guard()->user()) { + if ($user instanceof Model) { + $this->withActor($user); + } + } + + return $this->activity; + } +} diff --git a/app/Services/Activity/ActivityLogTargetableService.php b/app/Services/Activity/ActivityLogTargetableService.php new file mode 100644 index 0000000000..6883a4ce15 --- /dev/null +++ b/app/Services/Activity/ActivityLogTargetableService.php @@ -0,0 +1,47 @@ +actor)) { + throw new InvalidArgumentException('Cannot call ' . __METHOD__ . ' when an actor is already set on the instance.'); + } + + $this->actor = $actor; + } + + public function setSubject(Model $subject): void + { + if (!is_null($this->subject)) { + throw new InvalidArgumentException('Cannot call ' . __METHOD__ . ' when a target is already set on the instance.'); + } + + $this->subject = $subject; + } + + public function actor(): ?Model + { + return $this->actor; + } + + public function subject(): ?Model + { + return $this->subject; + } + + public function reset(): void + { + $this->actor = null; + $this->subject = null; + } +} diff --git a/config/app.php b/config/app.php index f57036f959..8d48b46316 100644 --- a/config/app.php +++ b/config/app.php @@ -173,6 +173,7 @@ /* * Application Service Providers... */ + Pterodactyl\Providers\ActivityLogServiceProvider::class, Pterodactyl\Providers\AppServiceProvider::class, Pterodactyl\Providers\AuthServiceProvider::class, Pterodactyl\Providers\BackupsServiceProvider::class, diff --git a/database/migrations/2022_05_28_135717_create_activity_logs_table.php b/database/migrations/2022_05_28_135717_create_activity_logs_table.php new file mode 100644 index 0000000000..570cc40602 --- /dev/null +++ b/database/migrations/2022_05_28_135717_create_activity_logs_table.php @@ -0,0 +1,37 @@ +id(); + $table->uuid('batch')->nullable(); + $table->string('event')->index(); + $table->text('description')->nullable(); + $table->nullableNumericMorphs('actor'); + $table->nullableNumericMorphs('subject'); + $table->json('properties')->nullable(); + $table->timestamp('timestamp')->useCurrent()->onUpdate(null); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('activity_logs'); + } +} From 0999ad7ff06e90f01b8d77a7d577dce27baafbab Mon Sep 17 00:00:00 2001 From: DaneEveritt Date: Sat, 28 May 2022 17:03:58 -0400 Subject: [PATCH 060/458] Add activity logging for authentication events --- .../Auth/ProvidedAuthenticationToken.php | 18 +++++++ .../Events/Contracts/SubscribesToEvents.php | 10 ++++ app/Facades/Activity.php | 11 +++-- .../Auth/LoginCheckpointController.php | 6 +++ app/Http/Controllers/Auth/LoginController.php | 3 ++ app/Listeners/Auth/AuthenticationListener.php | 40 ++++++++++++++++ app/Listeners/Auth/PasswordResetListener.php | 25 ++++++++++ app/Listeners/Auth/TwoFactorListener.php | 17 +++++++ app/Models/User.php | 6 +++ app/Providers/EventServiceProvider.php | 14 ++++-- app/Services/Activity/ActivityLogService.php | 47 ++++++++++++++----- 11 files changed, 179 insertions(+), 18 deletions(-) create mode 100644 app/Events/Auth/ProvidedAuthenticationToken.php create mode 100644 app/Extensions/Illuminate/Events/Contracts/SubscribesToEvents.php create mode 100644 app/Listeners/Auth/AuthenticationListener.php create mode 100644 app/Listeners/Auth/PasswordResetListener.php create mode 100644 app/Listeners/Auth/TwoFactorListener.php diff --git a/app/Events/Auth/ProvidedAuthenticationToken.php b/app/Events/Auth/ProvidedAuthenticationToken.php new file mode 100644 index 0000000000..71a1c6797c --- /dev/null +++ b/app/Events/Auth/ProvidedAuthenticationToken.php @@ -0,0 +1,18 @@ +user = $user; + $this->recovery = $recovery; + } +} diff --git a/app/Extensions/Illuminate/Events/Contracts/SubscribesToEvents.php b/app/Extensions/Illuminate/Events/Contracts/SubscribesToEvents.php new file mode 100644 index 0000000000..9d78252c99 --- /dev/null +++ b/app/Extensions/Illuminate/Events/Contracts/SubscribesToEvents.php @@ -0,0 +1,10 @@ +input('recovery_token'))) { if ($this->isValidRecoveryToken($user, $recoveryToken)) { + Event::dispatch(new ProvidedAuthenticationToken($user, true)); + return $this->sendLoginResponse($user, $request); } } else { $decrypted = $this->encrypter->decrypt($user->totp_secret); if ($this->google2FA->verifyKey($decrypted, (string) $request->input('authentication_code') ?? '', config('pterodactyl.auth.2fa.window'))) { + Event::dispatch(new ProvidedAuthenticationToken($user)); + return $this->sendLoginResponse($user, $request); } } diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php index b3ee4b2a42..f26e538495 100644 --- a/app/Http/Controllers/Auth/LoginController.php +++ b/app/Http/Controllers/Auth/LoginController.php @@ -7,6 +7,7 @@ use Illuminate\Http\Request; use Pterodactyl\Models\User; use Illuminate\Http\JsonResponse; +use Pterodactyl\Facades\Activity; use Illuminate\Contracts\View\View; use Illuminate\Contracts\View\Factory as ViewFactory; use Illuminate\Database\Eloquent\ModelNotFoundException; @@ -71,6 +72,8 @@ public function login(Request $request): JsonResponse return $this->sendLoginResponse($user, $request); } + Activity::event('login.checkpoint')->withRequestMetadata()->subject($user)->log(); + $request->session()->put('auth_confirmation_token', [ 'user_id' => $user->id, 'token_value' => $token = Str::random(64), diff --git a/app/Listeners/Auth/AuthenticationListener.php b/app/Listeners/Auth/AuthenticationListener.php new file mode 100644 index 0000000000..e19c828ffb --- /dev/null +++ b/app/Listeners/Auth/AuthenticationListener.php @@ -0,0 +1,40 @@ +user) { + $activity = $activity->subject($event->user); + } + + if ($event instanceof Failed) { + foreach ($event->credentials as $key => $value) { + $activity = $activity->property($key, $value); + } + } + + $activity->event($event instanceof Failed ? 'login.failed' : 'login.success')->log(); + } + + public function subscribe(Dispatcher $events): void + { + $events->listen(Failed::class, self::class); + $events->listen(Login::class, self::class); + } +} diff --git a/app/Listeners/Auth/PasswordResetListener.php b/app/Listeners/Auth/PasswordResetListener.php new file mode 100644 index 0000000000..54acbc0cf4 --- /dev/null +++ b/app/Listeners/Auth/PasswordResetListener.php @@ -0,0 +1,25 @@ +request = $request; + } + + public function handle(PasswordReset $event) + { + Activity::event('login.password-reset') + ->withRequestMetadata() + ->subject($event->user) + ->log(); + } +} diff --git a/app/Listeners/Auth/TwoFactorListener.php b/app/Listeners/Auth/TwoFactorListener.php new file mode 100644 index 0000000000..468c5da8d0 --- /dev/null +++ b/app/Listeners/Auth/TwoFactorListener.php @@ -0,0 +1,17 @@ +recovery ? 'login.recovery-token' : 'login.token') + ->withRequestMetadata() + ->subject($event->user) + ->log(); + } +} diff --git a/app/Models/User.php b/app/Models/User.php index fff28b9b14..c8fe64214d 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -3,6 +3,7 @@ namespace Pterodactyl\Models; use Pterodactyl\Rules\Username; +use Pterodactyl\Facades\Activity; use Illuminate\Support\Collection; use Illuminate\Validation\Rules\In; use Illuminate\Auth\Authenticatable; @@ -214,6 +215,11 @@ public function toVueObject(): array */ public function sendPasswordResetNotification($token) { + Activity::event('login.reset-password') + ->withRequestMetadata() + ->subject($this) + ->log('sending password reset email'); + $this->notify(new ResetPasswordNotification($token)); } diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index bf8dfc43d6..9a02cca3d7 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -10,6 +10,7 @@ use Pterodactyl\Observers\ServerObserver; use Pterodactyl\Observers\SubuserObserver; use Pterodactyl\Observers\EggVariableObserver; +use Pterodactyl\Listeners\Auth\AuthenticationListener; use Pterodactyl\Events\Server\Installed as ServerInstalledEvent; use Pterodactyl\Notifications\ServerInstalled as ServerInstalledNotification; use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider; @@ -22,9 +23,11 @@ class EventServiceProvider extends ServiceProvider * @var array */ protected $listen = [ - ServerInstalledEvent::class => [ - ServerInstalledNotification::class, - ], + ServerInstalledEvent::class => [ServerInstalledNotification::class], + ]; + + protected $subscribe = [ + AuthenticationListener::class, ]; /** @@ -39,4 +42,9 @@ public function boot() Subuser::observe(SubuserObserver::class); EggVariable::observe(EggVariableObserver::class); } + + public function shouldDiscoverEvents() + { + return true; + } } diff --git a/app/Services/Activity/ActivityLogService.php b/app/Services/Activity/ActivityLogService.php index d34262a336..42293b7e44 100644 --- a/app/Services/Activity/ActivityLogService.php +++ b/app/Services/Activity/ActivityLogService.php @@ -6,6 +6,7 @@ use Pterodactyl\Models\ActivityLog; use Illuminate\Contracts\Auth\Factory; use Illuminate\Database\Eloquent\Model; +use Illuminate\Support\Facades\Request; use Illuminate\Database\ConnectionInterface; class ActivityLogService @@ -55,7 +56,7 @@ public function event(string $action): self /** * Set the description for this activity. */ - public function withDescription(?string $description): self + public function description(?string $description): self { $this->getActivity()->description = $description; @@ -65,7 +66,7 @@ public function withDescription(?string $description): self /** * Sets the subject model instance. */ - public function withSubject(Model $subject): self + public function subject(Model $subject): self { $this->getActivity()->subject()->associate($subject); @@ -75,7 +76,7 @@ public function withSubject(Model $subject): self /** * Sets the actor model instance. */ - public function withActor(Model $actor): self + public function actor(Model $actor): self { $this->getActivity()->actor()->associate($actor); @@ -99,28 +100,52 @@ public function withProperties($properties): self * * @param mixed $value */ - public function withProperty(string $key, $value): self + public function property(string $key, $value): self { $this->getActivity()->properties = $this->getActivity()->properties->put($key, $value); return $this; } + /** + * Attachs the instance request metadata to the activity log event. + */ + public function withRequestMetadata(): self + { + $this->property('ip', Request::getClientIp()); + $this->property('useragent', Request::userAgent()); + + return $this; + } + /** * Logs an activity log entry with the set values and then returns the * model instance to the caller. */ - public function log(string $description): ActivityLog + public function log(string $description = null): ActivityLog { - $this->withDescription($description); + $activity = $this->getActivity(); + + if (!is_null($description)) { + $activity->description = $description; + } - $activity = $this->activity; $activity->save(); + $this->activity = null; return $activity; } + /** + * Returns a cloned instance of the service allowing for the creation of a base + * activity log with the ability to change values on the fly without impact. + */ + public function clone(): self + { + return clone $this; + } + /** * Executes the provided callback within the scope of a database transaction * and will only save the activity log entry if everything else succesfully @@ -133,7 +158,7 @@ public function log(string $description): ActivityLog public function transaction(\Closure $callback, string $description = null) { if (!is_null($description)) { - $this->withDescription($description); + $this->description($description); } return $this->connection->transaction(function () use ($callback) { @@ -161,14 +186,14 @@ protected function getActivity(): ActivityLog ]); if ($subject = $this->targetable->subject()) { - $this->withSubject($subject); + $this->subject($subject); } if ($actor = $this->targetable->actor()) { - $this->withActor($actor); + $this->actor($actor); } elseif ($user = $this->manager->guard()->user()) { if ($user instanceof Model) { - $this->withActor($user); + $this->actor($user); } } From cbecfff6da34cc01257d6db4d0c642c561a1672d Mon Sep 17 00:00:00 2001 From: DaneEveritt Date: Sun, 29 May 2022 13:56:39 -0400 Subject: [PATCH 061/458] Add activity logging for files --- .../Api/Client/Servers/FileController.php | 169 ++++++++---------- .../Client/Servers/FileUploadController.php | 3 + app/Models/ActivityLog.php | 10 +- app/Services/Activity/ActivityLogService.php | 1 + ...5_28_135717_create_activity_logs_table.php | 3 +- routes/api-client.php | 20 ++- 6 files changed, 98 insertions(+), 108 deletions(-) diff --git a/app/Http/Controllers/Api/Client/Servers/FileController.php b/app/Http/Controllers/Api/Client/Servers/FileController.php index 7e5ff45ee1..c2856ff091 100644 --- a/app/Http/Controllers/Api/Client/Servers/FileController.php +++ b/app/Http/Controllers/Api/Client/Servers/FileController.php @@ -5,10 +5,9 @@ use Carbon\CarbonImmutable; use Illuminate\Http\Response; use Pterodactyl\Models\Server; -use Pterodactyl\Models\AuditLog; use Illuminate\Http\JsonResponse; +use Pterodactyl\Facades\Activity; use Pterodactyl\Services\Nodes\NodeJWTService; -use Illuminate\Contracts\Routing\ResponseFactory; use Pterodactyl\Repositories\Wings\DaemonFileRepository; use Pterodactyl\Transformers\Api\Client\FileObjectTransformer; use Pterodactyl\Http\Controllers\Api\Client\ClientApiController; @@ -31,11 +30,6 @@ class FileController extends ClientApiController */ private $fileRepository; - /** - * @var \Illuminate\Contracts\Routing\ResponseFactory - */ - private $responseFactory; - /** * @var \Pterodactyl\Services\Nodes\NodeJWTService */ @@ -45,14 +39,12 @@ class FileController extends ClientApiController * FileController constructor. */ public function __construct( - ResponseFactory $responseFactory, NodeJWTService $jwtService, DaemonFileRepository $fileRepository ) { parent::__construct(); $this->fileRepository = $fileRepository; - $this->responseFactory = $responseFactory; $this->jwtService = $jwtService; } @@ -84,6 +76,8 @@ public function contents(GetFileContentsRequest $request, Server $server): Respo config('pterodactyl.files.max_edit_size') ); + Activity::event('server:file.read')->property('file', $request->get('file'))->log(); + return new Response($response, Response::HTTP_OK, ['Content-Type' => 'text/plain']); } @@ -97,20 +91,15 @@ public function contents(GetFileContentsRequest $request, Server $server): Respo */ public function download(GetFileContentsRequest $request, Server $server) { - $token = $server->audit( - AuditLog::SERVER__FILESYSTEM_DOWNLOAD, - function (AuditLog $audit, Server $server) use ($request) { - $audit->metadata = ['file' => $request->get('file')]; - - return $this->jwtService - ->setExpiresAt(CarbonImmutable::now()->addMinutes(15)) - ->setClaims([ - 'file_path' => rawurldecode($request->get('file')), - 'server_uuid' => $server->uuid, - ]) - ->handle($server->node, $request->user()->id . $server->uuid); - } - ); + $token = $this->jwtService + ->setExpiresAt(CarbonImmutable::now()->addMinutes(15)) + ->setClaims([ + 'file_path' => rawurldecode($request->get('file')), + 'server_uuid' => $server->uuid, + ]) + ->handle($server->node, $request->user()->id . $server->uuid); + + Activity::event('server:file.download')->property('file', $request->get('file'))->log(); return [ 'object' => 'signed_url', @@ -131,14 +120,9 @@ function (AuditLog $audit, Server $server) use ($request) { */ public function write(WriteFileContentRequest $request, Server $server): JsonResponse { - $server->audit(AuditLog::SERVER__FILESYSTEM_WRITE, function (AuditLog $audit, Server $server) use ($request) { - $audit->subaction = 'write_content'; - $audit->metadata = ['file' => $request->get('file')]; + $this->fileRepository->setServer($server)->putContent($request->get('file'), $request->getContent()); - $this->fileRepository - ->setServer($server) - ->putContent($request->get('file'), $request->getContent()); - }); + Activity::event('server:file.write')->property('file', $request->get('file'))->log(); return new JsonResponse([], Response::HTTP_NO_CONTENT); } @@ -150,14 +134,14 @@ public function write(WriteFileContentRequest $request, Server $server): JsonRes */ public function create(CreateFolderRequest $request, Server $server): JsonResponse { - $server->audit(AuditLog::SERVER__FILESYSTEM_WRITE, function (AuditLog $audit, Server $server) use ($request) { - $audit->subaction = 'create_folder'; - $audit->metadata = ['file' => $request->input('root', '/') . $request->input('name')]; + $this->fileRepository + ->setServer($server) + ->createDirectory($request->input('name'), $request->input('root', '/')); - $this->fileRepository - ->setServer($server) - ->createDirectory($request->input('name'), $request->input('root', '/')); - }); + Activity::event('server:file.create-directory') + ->property('name', $request->input('name')) + ->property('directory', $request->input('root')) + ->log(); return new JsonResponse([], Response::HTTP_NO_CONTENT); } @@ -169,13 +153,14 @@ public function create(CreateFolderRequest $request, Server $server): JsonRespon */ public function rename(RenameFileRequest $request, Server $server): JsonResponse { - $server->audit(AuditLog::SERVER__FILESYSTEM_RENAME, function (AuditLog $audit, Server $server) use ($request) { - $audit->metadata = ['root' => $request->input('root'), 'files' => $request->input('files')]; + $this->fileRepository + ->setServer($server) + ->renameFiles($request->input('root'), $request->input('files')); - $this->fileRepository - ->setServer($server) - ->renameFiles($request->input('root'), $request->input('files')); - }); + Activity::event('server:file.rename') + ->property('directory', $request->input('root')) + ->property('files', $request->input('files')) + ->log(); return new JsonResponse([], Response::HTTP_NO_CONTENT); } @@ -187,14 +172,11 @@ public function rename(RenameFileRequest $request, Server $server): JsonResponse */ public function copy(CopyFileRequest $request, Server $server): JsonResponse { - $server->audit(AuditLog::SERVER__FILESYSTEM_WRITE, function (AuditLog $audit, Server $server) use ($request) { - $audit->subaction = 'copy_file'; - $audit->metadata = ['file' => $request->input('location')]; + $this->fileRepository + ->setServer($server) + ->copyFile($request->input('location')); - $this->fileRepository - ->setServer($server) - ->copyFile($request->input('location')); - }); + Activity::event('server:file.copy')->property('file', $request->input('location'))->log(); return new JsonResponse([], Response::HTTP_NO_CONTENT); } @@ -204,22 +186,16 @@ public function copy(CopyFileRequest $request, Server $server): JsonResponse */ public function compress(CompressFilesRequest $request, Server $server): array { - $file = $server->audit( - AuditLog::SERVER__FILESYSTEM_COMPRESS, - function (AuditLog $audit, Server $server) use ($request) { - // Allow up to five minutes for this request to process before timing out. - set_time_limit(300); - - $audit->metadata = ['root' => $request->input('root'), 'files' => $request->input('files')]; - - return $this->fileRepository->setServer($server) - ->compressFiles( - $request->input('root'), - $request->input('files') - ); - } + $file = $this->fileRepository->setServer($server)->compressFiles( + $request->input('root'), + $request->input('files') ); + Activity::event('server:file.compress') + ->property('directory', $request->input('root')) + ->property('files', $request->input('files')) + ->log(); + return $this->fractal->item($file) ->transformWith($this->getTransformer(FileObjectTransformer::class)) ->toArray(); @@ -230,19 +206,18 @@ function (AuditLog $audit, Server $server) use ($request) { */ public function decompress(DecompressFilesRequest $request, Server $server): JsonResponse { - $file = $server->audit( - AuditLog::SERVER__FILESYSTEM_DECOMPRESS, - function (AuditLog $audit, Server $server) use ($request) { - // Allow up to five minutes for this request to process before timing out. - set_time_limit(300); + set_time_limit(300); - $audit->metadata = ['root' => $request->input('root'), 'files' => $request->input('file')]; - - $this->fileRepository->setServer($server) - ->decompressFile($request->input('root'), $request->input('file')); - } + $this->fileRepository->setServer($server)->decompressFile( + $request->input('root'), + $request->input('file') ); + Activity::event('server:file.decompress') + ->property('directory', $request->input('root')) + ->property('files', $request->input('file')) + ->log(); + return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT); } @@ -253,15 +228,15 @@ function (AuditLog $audit, Server $server) use ($request) { */ public function delete(DeleteFileRequest $request, Server $server): JsonResponse { - $server->audit(AuditLog::SERVER__FILESYSTEM_DELETE, function (AuditLog $audit, Server $server) use ($request) { - $audit->metadata = ['root' => $request->input('root'), 'files' => $request->input('files')]; + $this->fileRepository->setServer($server)->deleteFiles( + $request->input('root'), + $request->input('files') + ); - $this->fileRepository->setServer($server) - ->deleteFiles( - $request->input('root'), - $request->input('files') - ); - }); + Activity::event('server:file.delete') + ->property('directory', $request->input('root')) + ->property('files', $request->input('files')) + ->log(); return new JsonResponse([], Response::HTTP_NO_CONTENT); } @@ -273,11 +248,10 @@ public function delete(DeleteFileRequest $request, Server $server): JsonResponse */ public function chmod(ChmodFilesRequest $request, Server $server): JsonResponse { - $this->fileRepository->setServer($server) - ->chmodFiles( - $request->input('root'), - $request->input('files') - ); + $this->fileRepository->setServer($server)->chmodFiles( + $request->input('root'), + $request->input('files') + ); return new JsonResponse([], Response::HTTP_NO_CONTENT); } @@ -289,17 +263,16 @@ public function chmod(ChmodFilesRequest $request, Server $server): JsonResponse */ public function pull(PullFileRequest $request, Server $server): JsonResponse { - $server->audit(AuditLog::SERVER__FILESYSTEM_PULL, function (AuditLog $audit, Server $server) use ($request) { - $audit->metadata = ['directory' => $request->input('directory'), 'url' => $request->input('url')]; - - $this->fileRepository - ->setServer($server) - ->pull( - $request->input('url'), - $request->input('directory'), - $request->safe(['filename', 'use_header', 'foreground']) - ); - }); + $this->fileRepository->setServer($server)->pull( + $request->input('url'), + $request->input('directory'), + $request->safe(['filename', 'use_header', 'foreground']) + ); + + Activity::event('server:file.pull') + ->property('directory', $request->input('directory')) + ->property('url', $request->input('url')) + ->log(); return new JsonResponse([], Response::HTTP_NO_CONTENT); } diff --git a/app/Http/Controllers/Api/Client/Servers/FileUploadController.php b/app/Http/Controllers/Api/Client/Servers/FileUploadController.php index 236273c75e..a3b362520b 100644 --- a/app/Http/Controllers/Api/Client/Servers/FileUploadController.php +++ b/app/Http/Controllers/Api/Client/Servers/FileUploadController.php @@ -6,6 +6,7 @@ use Pterodactyl\Models\User; use Pterodactyl\Models\Server; use Illuminate\Http\JsonResponse; +use Pterodactyl\Facades\Activity; use Pterodactyl\Services\Nodes\NodeJWTService; use Pterodactyl\Http\Controllers\Api\Client\ClientApiController; use Pterodactyl\Http\Requests\Api\Client\Servers\Files\UploadFileRequest; @@ -35,6 +36,8 @@ public function __construct( */ public function __invoke(UploadFileRequest $request, Server $server) { + Activity::event('server:file.upload')->log(); + return new JsonResponse([ 'object' => 'signed_url', 'attributes' => [ diff --git a/app/Models/ActivityLog.php b/app/Models/ActivityLog.php index f58d827c65..47ef911cac 100644 --- a/app/Models/ActivityLog.php +++ b/app/Models/ActivityLog.php @@ -12,6 +12,7 @@ * @property int $id * @property string|null $batch * @property string $event + * @property string $ip * @property string|null $description * @property string|null $actor_type * @property int|null $actor_id @@ -19,8 +20,8 @@ * @property int|null $subject_id * @property \Illuminate\Support\Collection $properties * @property string $timestamp - * @property \Illuminate\Database\Eloquent\Model|\Eloquent $actor - * @property \Illuminate\Database\Eloquent\Model|\Eloquent $subject + * @property IlluminateModel|\Eloquent $actor + * @property IlluminateModel|\Eloquent $subject * * @method static Builder|ActivityLog forAction(string $action) * @method static Builder|ActivityLog forActor(\Illuminate\Database\Eloquent\Model $actor) @@ -28,11 +29,11 @@ * @method static Builder|ActivityLog newModelQuery() * @method static Builder|ActivityLog newQuery() * @method static Builder|ActivityLog query() - * @method static Builder|ActivityLog whereAction($value) * @method static Builder|ActivityLog whereActorId($value) * @method static Builder|ActivityLog whereActorType($value) * @method static Builder|ActivityLog whereBatch($value) * @method static Builder|ActivityLog whereDescription($value) + * @method static Builder|ActivityLog whereEvent($value) * @method static Builder|ActivityLog whereId($value) * @method static Builder|ActivityLog whereIp($value) * @method static Builder|ActivityLog whereProperties($value) @@ -57,8 +58,9 @@ class ActivityLog extends Model public static $validationRules = [ 'event' => ['required', 'string'], 'batch' => ['nullable', 'uuid'], + 'ip' => ['required', 'string'], 'description' => ['nullable', 'string'], - 'properties' => ['nullable', 'array'], + 'properties' => ['array'], ]; public function actor(): MorphTo diff --git a/app/Services/Activity/ActivityLogService.php b/app/Services/Activity/ActivityLogService.php index 42293b7e44..8ad5af53be 100644 --- a/app/Services/Activity/ActivityLogService.php +++ b/app/Services/Activity/ActivityLogService.php @@ -181,6 +181,7 @@ protected function getActivity(): ActivityLog } $this->activity = new ActivityLog([ + 'ip' => Request::ip(), 'batch_uuid' => $this->batch->uuid(), 'properties' => Collection::make([]), ]); diff --git a/database/migrations/2022_05_28_135717_create_activity_logs_table.php b/database/migrations/2022_05_28_135717_create_activity_logs_table.php index 570cc40602..0624a5a663 100644 --- a/database/migrations/2022_05_28_135717_create_activity_logs_table.php +++ b/database/migrations/2022_05_28_135717_create_activity_logs_table.php @@ -17,10 +17,11 @@ public function up() $table->id(); $table->uuid('batch')->nullable(); $table->string('event')->index(); + $table->string('ip'); $table->text('description')->nullable(); $table->nullableNumericMorphs('actor'); $table->nullableNumericMorphs('subject'); - $table->json('properties')->nullable(); + $table->json('properties'); $table->timestamp('timestamp')->useCurrent()->onUpdate(null); }); } diff --git a/routes/api-client.php b/routes/api-client.php index 5c2bfa9b16..b592ea4203 100644 --- a/routes/api-client.php +++ b/routes/api-client.php @@ -2,6 +2,7 @@ use Illuminate\Support\Facades\Route; use Pterodactyl\Http\Controllers\Api\Client; +use Pterodactyl\Http\Middleware\ServerActivityLogs; use Pterodactyl\Http\Middleware\RequireTwoFactorAuthentication; use Pterodactyl\Http\Middleware\Api\Client\Server\ResourceBelongsToServer; use Pterodactyl\Http\Middleware\Api\Client\Server\AuthenticateServerAccess; @@ -18,10 +19,12 @@ Route::get('/permissions', [Client\ClientController::class, 'permissions']); Route::group(['prefix' => '/account'], function () { - Route::get('/', [Client\AccountController::class, 'index'])->name('api:client.account')->withoutMiddleware(RequireTwoFactorAuthentication::class); - Route::get('/two-factor', [Client\TwoFactorController::class, 'index'])->withoutMiddleware(RequireTwoFactorAuthentication::class); - Route::post('/two-factor', [Client\TwoFactorController::class, 'store'])->withoutMiddleware(RequireTwoFactorAuthentication::class); - Route::delete('/two-factor', [Client\TwoFactorController::class, 'delete'])->withoutMiddleware(RequireTwoFactorAuthentication::class); + Route::prefix('/')->withoutMiddleware(RequireTwoFactorAuthentication::class)->group(function () { + Route::get('/', [Client\AccountController::class, 'index'])->name('api:client.account'); + Route::get('/two-factor', [Client\TwoFactorController::class, 'index']); + Route::post('/two-factor', [Client\TwoFactorController::class, 'store']); + Route::delete('/two-factor', [Client\TwoFactorController::class, 'delete']); + }); Route::put('/email', [Client\AccountController::class, 'updateEmail'])->name('api:client.account.update-email'); Route::put('/password', [Client\AccountController::class, 'updatePassword'])->name('api:client.account.update-password'); @@ -45,7 +48,14 @@ | Endpoint: /api/client/servers/{server} | */ -Route::group(['prefix' => '/servers/{server}', 'middleware' => [AuthenticateServerAccess::class, ResourceBelongsToServer::class]], function () { +Route::group([ + 'prefix' => '/servers/{server}', + 'middleware' => [ + ServerActivityLogs::class, + AuthenticateServerAccess::class, + ResourceBelongsToServer::class, + ], +], function () { Route::get('/', [Client\Servers\ServerController::class, 'index'])->name('api:client:server.view'); Route::get('/websocket', Client\Servers\WebsocketController::class)->name('api:client:server.ws'); Route::get('/resources', Client\Servers\ResourceUtilizationController::class)->name('api:client:server.resources'); From 2fc5a734f9a2f292f623a661f460c6a792195d64 Mon Sep 17 00:00:00 2001 From: DaneEveritt Date: Sun, 29 May 2022 16:19:04 -0400 Subject: [PATCH 062/458] Update backup logic to use activity logs, not audit logs --- app/Facades/Activity.php | 5 +- .../Api/Client/Servers/BackupController.php | 63 ++++++----- .../Remote/Backups/BackupStatusController.php | 32 +++--- .../Servers/ServerDetailsController.php | 20 ++-- app/Models/ActivityLog.php | 24 +---- app/Models/ActivityLogSubject.php | 40 +++++++ app/Models/Server.php | 47 +------- app/Models/User.php | 10 ++ app/Providers/AppServiceProvider.php | 10 ++ app/Services/Activity/ActivityLogService.php | 101 ++++++++++++------ ...5_28_135717_create_activity_logs_table.php | 1 - ...40349_create_activity_log_actors_table.php | 32 ++++++ 12 files changed, 224 insertions(+), 161 deletions(-) create mode 100644 app/Models/ActivityLogSubject.php create mode 100644 database/migrations/2022_05_29_140349_create_activity_log_actors_table.php diff --git a/app/Facades/Activity.php b/app/Facades/Activity.php index 3dfb49aa19..86e2bd31c1 100644 --- a/app/Facades/Activity.php +++ b/app/Facades/Activity.php @@ -10,11 +10,10 @@ * @method static ActivityLogService anonymous() * @method static ActivityLogService event(string $action) * @method static ActivityLogService description(?string $description) - * @method static ActivityLogService subject(Model $subject) + * @method static ActivityLogService subject(Model|Model[] $subject) * @method static ActivityLogService actor(Model $actor) - * @method static ActivityLogService withProperties(\Illuminate\Support\Collection|array $properties) * @method static ActivityLogService withRequestMetadata() - * @method static ActivityLogService property(string $key, mixed $value) + * @method static ActivityLogService property(string|array $key, mixed $value = null) * @method static \Pterodactyl\Models\ActivityLog log(string $description = null) * @method static ActivityLogService clone() * @method static mixed transaction(\Closure $callback) diff --git a/app/Http/Controllers/Api/Client/Servers/BackupController.php b/app/Http/Controllers/Api/Client/Servers/BackupController.php index 67b54cd287..bfd9b68ba7 100644 --- a/app/Http/Controllers/Api/Client/Servers/BackupController.php +++ b/app/Http/Controllers/Api/Client/Servers/BackupController.php @@ -5,8 +5,8 @@ use Illuminate\Http\Request; use Pterodactyl\Models\Backup; use Pterodactyl\Models\Server; -use Pterodactyl\Models\AuditLog; use Illuminate\Http\JsonResponse; +use Pterodactyl\Facades\Activity; use Pterodactyl\Models\Permission; use Illuminate\Auth\Access\AuthorizationException; use Pterodactyl\Services\Backups\DeleteBackupService; @@ -77,25 +77,23 @@ public function index(Request $request, Server $server): array */ public function store(StoreBackupRequest $request, Server $server): array { - /** @var \Pterodactyl\Models\Backup $backup */ - $backup = $server->audit(AuditLog::SERVER__BACKUP_STARTED, function (AuditLog $model, Server $server) use ($request) { - $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')); + $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')); + } - $model->metadata = ['backup_uuid' => $backup->uuid]; + $backup = $action->handle($server, $request->input('name')); - return $backup; - }); + Activity::event('server:backup.start') + ->subject($backup) + ->property(['name' => $backup->name, 'locked' => (bool) $request->input('is_locked')]) + ->log(); return $this->fractal->item($backup) ->transformWith($this->getTransformer(BackupTransformer::class)) @@ -114,14 +112,11 @@ public function toggleLock(Request $request, Server $server, Backup $backup): ar 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]; + $action = $backup->is_locked ? 'server:backup.unlock' : 'server:backup.lock'; - $backup->update(['is_locked' => !$backup->is_locked]); - }); + $backup->update(['is_locked' => !$backup->is_locked]); - $backup->refresh(); + Activity::event($action)->subject($backup)->property('name', $backup->name)->log(); return $this->fractal->item($backup) ->transformWith($this->getTransformer(BackupTransformer::class)) @@ -156,11 +151,12 @@ public function delete(Request $request, Server $server, Backup $backup): JsonRe throw new AuthorizationException(); } - $server->audit(AuditLog::SERVER__BACKUP_DELETED, function (AuditLog $audit) use ($backup) { - $audit->metadata = ['backup_uuid' => $backup->uuid]; + $this->deleteBackupService->handle($backup); - $this->deleteBackupService->handle($backup); - }); + Activity::event('server:backup.delete') + ->subject($backup) + ->property(['name' => $backup->name, 'failed' => !$backup->is_successful]) + ->log(); return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT); } @@ -184,9 +180,8 @@ public function download(Request $request, Server $server, Backup $backup): Json } $url = $this->downloadLinkService->handle($backup, $request->user()); - $server->audit(AuditLog::SERVER__BACKUP_DOWNLOADED, function (AuditLog $audit) use ($backup) { - $audit->metadata = ['backup_uuid' => $backup->uuid]; - }); + + Activity::event('server:backup.download')->subject($backup)->property('name', $backup->name)->log(); return new JsonResponse([ 'object' => 'signed_url', @@ -221,9 +216,11 @@ public function restore(Request $request, Server $server, Backup $backup): JsonR throw new BadRequestHttpException('This backup cannot be restored at this time: not completed or failed.'); } - $server->audit(AuditLog::SERVER__BACKUP_RESTORE_STARTED, function (AuditLog $audit, Server $server) use ($backup, $request) { - $audit->metadata = ['backup_uuid' => $backup->uuid]; + $log = Activity::event('server:backup.restore') + ->subject($backup) + ->property(['name' => $backup->name, 'truncate' => $request->input('truncate')]); + $log->transaction(function () use ($backup, $server, $request) { // If the backup is for an S3 file we need to generate a unique Download link for // it that will allow Wings to actually access the file. if ($backup->disk === Backup::ADAPTER_AWS_S3) { diff --git a/app/Http/Controllers/Api/Remote/Backups/BackupStatusController.php b/app/Http/Controllers/Api/Remote/Backups/BackupStatusController.php index f4e7bd6eab..17f62329fd 100644 --- a/app/Http/Controllers/Api/Remote/Backups/BackupStatusController.php +++ b/app/Http/Controllers/Api/Remote/Backups/BackupStatusController.php @@ -5,9 +5,8 @@ use Carbon\CarbonImmutable; use Illuminate\Http\Request; use Pterodactyl\Models\Backup; -use Pterodactyl\Models\Server; -use Pterodactyl\Models\AuditLog; use Illuminate\Http\JsonResponse; +use Pterodactyl\Facades\Activity; use League\Flysystem\AwsS3v3\AwsS3Adapter; use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Http\Controllers\Controller; @@ -46,15 +45,12 @@ public function index(ReportBackupCompleteRequest $request, string $backup) throw new BadRequestHttpException('Cannot update the status of a backup that is already marked as completed.'); } - $action = $request->input('successful') - ? AuditLog::SERVER__BACKUP_COMPELTED - : AuditLog::SERVER__BACKUP_FAILED; - - $model->server->audit($action, function (AuditLog $audit) use ($model, $request) { - $audit->is_system = true; - $audit->metadata = ['backup_uuid' => $model->uuid]; + $action = $request->boolean('successful') ? 'server:backup.complete' : 'server:backup.failed'; + $log = Activity::event($action)->subject($model, $model->server)->property('name', $model->name); + $log->transaction(function () use ($model, $request) { $successful = $request->boolean('successful'); + $model->fill([ 'is_successful' => $successful, // Change the lock state to unlocked if this was a failed backup so that it can be @@ -93,17 +89,13 @@ public function restore(Request $request, string $backup) { /** @var \Pterodactyl\Models\Backup $model */ $model = Backup::query()->where('uuid', $backup)->firstOrFail(); - $action = $request->get('successful') - ? AuditLog::SERVER__BACKUP_RESTORE_COMPLETED - : AuditLog::SERVER__BACKUP_RESTORE_FAILED; - - // Just create a new audit entry for this event and update the server state - // so that power actions, file management, and backups can resume as normal. - $model->server->audit($action, function (AuditLog $audit, Server $server) use ($backup) { - $audit->is_system = true; - $audit->metadata = ['backup_uuid' => $backup]; - $server->update(['status' => null]); - }); + + $model->server->update(['status' => null]); + + Activity::event($request->boolean('successful') ? 'server:backup.restore-complete' : 'server.backup.restore-failed') + ->subject($model, $model->server) + ->property('name', $model->name) + ->log(); return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT); } diff --git a/app/Http/Controllers/Api/Remote/Servers/ServerDetailsController.php b/app/Http/Controllers/Api/Remote/Servers/ServerDetailsController.php index 6b49fafa3e..0e93c60cac 100644 --- a/app/Http/Controllers/Api/Remote/Servers/ServerDetailsController.php +++ b/app/Http/Controllers/Api/Remote/Servers/ServerDetailsController.php @@ -4,8 +4,10 @@ use Illuminate\Http\Request; use Pterodactyl\Models\Server; +use Pterodactyl\Models\Backup; use Pterodactyl\Models\AuditLog; use Illuminate\Http\JsonResponse; +use Pterodactyl\Facades\Activity; use Illuminate\Database\Query\Builder; use Illuminate\Database\Query\JoinClause; use Pterodactyl\Http\Controllers\Controller; @@ -107,7 +109,6 @@ public function resetState(Request $request) // // For each of those servers we'll track a new audit log entry to mark them as // failed and then update them all to be in a valid state. - /** @var \Pterodactyl\Models\Server[] $servers */ $servers = Server::query() ->select('servers.*') ->selectRaw('JSON_UNQUOTE(JSON_EXTRACT(started.metadata, "$.backup_uuid")) as backup_uuid') @@ -130,14 +131,17 @@ public function resetState(Request $request) ->where('servers.status', Server::STATUS_RESTORING_BACKUP) ->get(); + $backups = Backup::query()->whereIn('uuid', $servers->pluck('backup_uuid'))->get(); + + /** @var \Pterodactyl\Models\Server $server */ foreach ($servers as $server) { - // Just create a new audit entry for this event and update the server state - // so that power actions, file management, and backups can resume as normal. - $server->audit(AuditLog::SERVER__BACKUP_RESTORE_FAILED, function (AuditLog $audit, Server $server) { - $audit->is_system = true; - $audit->metadata = ['backup_uuid' => $server->getAttribute('backup_uuid')]; - $server->update(['status' => null]); - }); + $server->update(['status' => null]); + + if ($backup = $backups->where('uuid', $server->getAttribute('backup_uuid'))->first()) { + // Just create a new audit entry for this event and update the server state + // so that power actions, file management, and backups can resume as normal. + Activity::event('server:backup.restore-failed')->subject($server, $backup)->log(); + } } // Update any server marked as installing or restoring as being in a normal state diff --git a/app/Models/ActivityLog.php b/app/Models/ActivityLog.php index 47ef911cac..c8be7249ec 100644 --- a/app/Models/ActivityLog.php +++ b/app/Models/ActivityLog.php @@ -16,16 +16,13 @@ * @property string|null $description * @property string|null $actor_type * @property int|null $actor_id - * @property string|null $subject_type - * @property int|null $subject_id * @property \Illuminate\Support\Collection $properties * @property string $timestamp * @property IlluminateModel|\Eloquent $actor * @property IlluminateModel|\Eloquent $subject * - * @method static Builder|ActivityLog forAction(string $action) + * @method static Builder|ActivityLog forEvent(string $event) * @method static Builder|ActivityLog forActor(\Illuminate\Database\Eloquent\Model $actor) - * @method static Builder|ActivityLog forSubject(\Illuminate\Database\Eloquent\Model $subject) * @method static Builder|ActivityLog newModelQuery() * @method static Builder|ActivityLog newQuery() * @method static Builder|ActivityLog query() @@ -37,8 +34,6 @@ * @method static Builder|ActivityLog whereId($value) * @method static Builder|ActivityLog whereIp($value) * @method static Builder|ActivityLog whereProperties($value) - * @method static Builder|ActivityLog whereSubjectId($value) - * @method static Builder|ActivityLog whereSubjectType($value) * @method static Builder|ActivityLog whereTimestamp($value) * @mixin \Eloquent */ @@ -68,14 +63,9 @@ public function actor(): MorphTo return $this->morphTo(); } - public function subject(): MorphTo + public function scopeForEvent(Builder $builder, string $action): Builder { - return $this->morphTo(); - } - - public function scopeForAction(Builder $builder, string $action): Builder - { - return $builder->where('action', $action); + return $builder->where('event', $action); } /** @@ -85,12 +75,4 @@ public function scopeForActor(Builder $builder, IlluminateModel $actor): Builder { return $builder->whereMorphedTo('actor', $actor); } - - /** - * Scopes a query to only return results where the subject is the given model. - */ - public function scopeForSubject(Builder $builder, IlluminateModel $subject): Builder - { - return $builder->whereMorphedTo('subject', $subject); - } } diff --git a/app/Models/ActivityLogSubject.php b/app/Models/ActivityLogSubject.php new file mode 100644 index 0000000000..47264dbd61 --- /dev/null +++ b/app/Models/ActivityLogSubject.php @@ -0,0 +1,40 @@ +belongsTo(ActivityLog::class); + } + + public function subject() + { + return $this->morphTo(); + } +} diff --git a/app/Models/Server.php b/app/Models/Server.php index b713158323..7fe28674a4 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -2,10 +2,10 @@ namespace Pterodactyl\Models; -use Closure; use Illuminate\Notifications\Notifiable; use Illuminate\Database\Query\JoinClause; use Znck\Eloquent\Traits\BelongsToThrough; +use Illuminate\Database\Eloquent\Relations\MorphToMany; use Pterodactyl\Exceptions\Http\Server\ServerStateConflictException; /** @@ -41,8 +41,6 @@ * @property \Pterodactyl\Models\Allocation|null $allocation * @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\Allocation[] $allocations * @property int|null $allocations_count - * @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\AuditLog[] $audits - * @property int|null $audits_count * @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\Backup[] $backups * @property int|null $backups_count * @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\Database[] $databases @@ -373,48 +371,11 @@ public function mounts() } /** - * Returns a fresh AuditLog model for the server. This model is not saved to the - * database when created, so it is up to the caller to correctly store it as needed. - * - * @return \Pterodactyl\Models\AuditLog - */ - public function newAuditEvent(string $action, array $metadata = []): AuditLog - { - return AuditLog::instance($action, $metadata)->fill([ - 'server_id' => $this->id, - ]); - } - - /** - * Stores a new audit event for a server by using a transaction. If the transaction - * fails for any reason everything executed within will be rolled back. The callback - * passed in will receive the AuditLog model before it is saved and the second argument - * will be the current server instance. The callback should modify the audit entry as - * needed before finishing, any changes will be persisted. - * - * The response from the callback is returned to the caller. - * - * @return mixed - * - * @throws \Throwable - */ - public function audit(string $action, Closure $callback) - { - return $this->getConnection()->transaction(function () use ($action, $callback) { - $model = $this->newAuditEvent($action); - $response = $callback($model, $this); - $model->save(); - - return $response; - }); - } - - /** - * @return \Illuminate\Database\Eloquent\Relations\HasMany + * Returns all of the activity log entries where the server is the subject. */ - public function audits() + public function activity(): MorphToMany { - return $this->hasMany(AuditLog::class); + return $this->morphToMany(ActivityLog::class, 'subject', 'activity_log_subjects'); } /** diff --git a/app/Models/User.php b/app/Models/User.php index c8fe64214d..0570bbf314 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -14,6 +14,7 @@ use Pterodactyl\Traits\Helpers\AvailableLanguages; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Foundation\Auth\Access\Authorizable; +use Illuminate\Database\Eloquent\Relations\MorphToMany; use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract; use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract; use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract; @@ -273,6 +274,15 @@ public function sshKeys(): HasMany return $this->hasMany(UserSSHKey::class); } + /** + * Returns all of the activity logs where this user is the subject — not to + * be confused by activity logs where this user is the _actor_. + */ + public function activity(): MorphToMany + { + return $this->morphToMany(ActivityLog::class, 'subject', 'activity_log_subjects'); + } + /** * Returns all of the servers that a user can access by way of being the owner of the * server, or because they are assigned as a subuser for that server. diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 51fa6e7fd7..a55aa297e8 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -5,11 +5,15 @@ use View; use Cache; use Illuminate\Support\Str; +use Pterodactyl\Models\User; +use Pterodactyl\Models\Server; +use Pterodactyl\Models\Backup; use Illuminate\Support\Facades\URL; use Illuminate\Pagination\Paginator; use Illuminate\Support\Facades\Schema; use Illuminate\Support\ServiceProvider; use Pterodactyl\Extensions\Themes\Theme; +use Illuminate\Database\Eloquent\Relations\Relation; class AppServiceProvider extends ServiceProvider { @@ -33,6 +37,12 @@ public function boot() if (Str::startsWith(config('app.url') ?? '', 'https://')) { URL::forceScheme('https'); } + + Relation::enforceMorphMap([ + 'backup' => Backup::class, + 'server' => Server::class, + 'user' => User::class, + ]); } /** diff --git a/app/Services/Activity/ActivityLogService.php b/app/Services/Activity/ActivityLogService.php index 8ad5af53be..7c8227d2e9 100644 --- a/app/Services/Activity/ActivityLogService.php +++ b/app/Services/Activity/ActivityLogService.php @@ -2,20 +2,28 @@ namespace Pterodactyl\Services\Activity; +use Illuminate\Support\Arr; +use Webmozart\Assert\Assert; use Illuminate\Support\Collection; use Pterodactyl\Models\ActivityLog; use Illuminate\Contracts\Auth\Factory; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Facades\Request; +use Pterodactyl\Models\ActivityLogSubject; use Illuminate\Database\ConnectionInterface; class ActivityLogService { protected ?ActivityLog $activity = null; + protected array $subjects = []; + protected Factory $manager; + protected ConnectionInterface $connection; + protected AcitvityLogBatchService $batch; + protected ActivityLogTargetableService $targetable; public function __construct( @@ -65,10 +73,22 @@ public function description(?string $description): self /** * Sets the subject model instance. + * + * @param \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Model[] $subjects */ - public function subject(Model $subject): self + public function subject(...$subjects): self { - $this->getActivity()->subject()->associate($subject); + foreach (Arr::wrap($subjects) as $subject) { + foreach ($this->subjects as $entry) { + // If this subject is already tracked in our array of subjects just skip over + // it and move on to the next one in the list. + if ($entry->is($subject)) { + continue 2; + } + } + + $this->subjects[] = $subject; + } return $this; } @@ -83,26 +103,18 @@ public function actor(Model $actor): self return $this; } - /** - * Sets the custom properties for the activity log instance. - * - * @param \Illuminate\Support\Collection|array $properties - */ - public function withProperties($properties): self - { - $this->getActivity()->properties = Collection::make($properties); - - return $this; - } - /** * Sets a custom property on the activty log instance. * + * @param string|array $key * @param mixed $value */ - public function property(string $key, $value): self + public function property($key, $value = null): self { - $this->getActivity()->properties = $this->getActivity()->properties->put($key, $value); + $properties = $this->getActivity()->properties; + $this->activity->properties = is_array($key) + ? $properties->merge($key) + : $properties->put($key, $value); return $this; } @@ -112,10 +124,10 @@ public function property(string $key, $value): self */ public function withRequestMetadata(): self { - $this->property('ip', Request::getClientIp()); - $this->property('useragent', Request::userAgent()); - - return $this; + return $this->property([ + 'ip' => Request::getClientIp(), + 'useragent' => Request::userAgent(), + ]); } /** @@ -130,11 +142,7 @@ public function log(string $description = null): ActivityLog $activity->description = $description; } - $activity->save(); - - $this->activity = null; - - return $activity; + return $this->save(); } /** @@ -155,17 +163,12 @@ public function clone(): self * * @throws \Throwable */ - public function transaction(\Closure $callback, string $description = null) + public function transaction(\Closure $callback) { - if (!is_null($description)) { - $this->description($description); - } - return $this->connection->transaction(function () use ($callback) { $response = $callback($activity = $this->getActivity()); - $activity->save(); - $this->activity = null; + $this->save($activity); return $response; }); @@ -200,4 +203,38 @@ protected function getActivity(): ActivityLog return $this->activity; } + + /** + * Saves the activity log instance and attaches all of the subject models. + * + * @throws \Throwable + */ + protected function save(ActivityLog $activity = null): ActivityLog + { + $activity = $activity ?? $this->activity; + + Assert::notNull($activity); + + $response = $this->connection->transaction(function () use ($activity) { + $activity->save(); + + $subjects = Collection::make($this->subjects) + ->map(fn (Model $subject) => [ + 'activity_log_id' => $this->activity->id, + 'subject_id' => $subject->getKey(), + 'subject_type' => $subject->getMorphClass(), + ]) + ->values() + ->toArray(); + + ActivityLogSubject::insert($subjects); + + return $activity; + }); + + $this->activity = null; + $this->subjects = []; + + return $response; + } } diff --git a/database/migrations/2022_05_28_135717_create_activity_logs_table.php b/database/migrations/2022_05_28_135717_create_activity_logs_table.php index 0624a5a663..448439dc81 100644 --- a/database/migrations/2022_05_28_135717_create_activity_logs_table.php +++ b/database/migrations/2022_05_28_135717_create_activity_logs_table.php @@ -20,7 +20,6 @@ public function up() $table->string('ip'); $table->text('description')->nullable(); $table->nullableNumericMorphs('actor'); - $table->nullableNumericMorphs('subject'); $table->json('properties'); $table->timestamp('timestamp')->useCurrent()->onUpdate(null); }); diff --git a/database/migrations/2022_05_29_140349_create_activity_log_actors_table.php b/database/migrations/2022_05_29_140349_create_activity_log_actors_table.php new file mode 100644 index 0000000000..8be57bc1c9 --- /dev/null +++ b/database/migrations/2022_05_29_140349_create_activity_log_actors_table.php @@ -0,0 +1,32 @@ +id(); + $table->foreignId('activity_log_id')->references('id')->on('activity_logs'); + $table->numericMorphs('subject'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('activity_log_subject'); + } +} From f1c169999401d2709ecfb61c721dc53a0405b357 Mon Sep 17 00:00:00 2001 From: DaneEveritt Date: Sun, 29 May 2022 17:07:34 -0400 Subject: [PATCH 063/458] Fix tests with model events --- database/Factories/AllocationFactory.php | 2 +- database/Factories/ServerFactory.php | 7 +++++-- tests/Integration/IntegrationTestCase.php | 23 +---------------------- 3 files changed, 7 insertions(+), 25 deletions(-) diff --git a/database/Factories/AllocationFactory.php b/database/Factories/AllocationFactory.php index 9ed42e3ef8..c65298ec45 100644 --- a/database/Factories/AllocationFactory.php +++ b/database/Factories/AllocationFactory.php @@ -22,7 +22,7 @@ public function definition(): array { return [ 'ip' => $this->faker->unique()->ipv4, - 'port' => $this->faker->unique()->randomNumber(5), + 'port' => $this->faker->unique()->numberBetween(10000, 20000), ]; } diff --git a/database/Factories/ServerFactory.php b/database/Factories/ServerFactory.php index d39d614b2d..b9145c679d 100644 --- a/database/Factories/ServerFactory.php +++ b/database/Factories/ServerFactory.php @@ -38,8 +38,11 @@ public function definition() 'cpu' => 0, 'threads' => null, 'oom_disabled' => 0, - 'allocation_limit' => null, - 'database_limit' => null, + 'startup' => '/bin/bash echo "hello world"', + 'image' => 'foo/bar:latest', + 'allocation_limit' => 0, + 'database_limit' => 0, + 'backup_limit' => 0, 'created_at' => Carbon::now(), 'updated_at' => Carbon::now(), ]; diff --git a/tests/Integration/IntegrationTestCase.php b/tests/Integration/IntegrationTestCase.php index f12eaaef13..c290f6070b 100644 --- a/tests/Integration/IntegrationTestCase.php +++ b/tests/Integration/IntegrationTestCase.php @@ -4,7 +4,6 @@ use Carbon\CarbonImmutable; use Pterodactyl\Tests\TestCase; -use Illuminate\Database\Eloquent\Model; use Pterodactyl\Tests\Traits\Integration\CreatesTestModels; use Pterodactyl\Transformers\Api\Application\BaseTransformer; @@ -12,27 +11,7 @@ abstract class IntegrationTestCase extends TestCase { use CreatesTestModels; - /** - * Setup base integration test cases. - */ - public function setUp(): void - { - parent::setUp(); - - // Disable event dispatcher to prevent eloquence from trying to - // perform validation on models going into the database. If this is - // not disabled, eloquence validation errors get swallowed and - // the tests cannot complete because nothing is put into the database. - Model::unsetEventDispatcher(); - } - - /** - * @return array - */ - protected function connectionsToTransact() - { - return ['testing']; - } + protected array $connectionsToTransact = ['mysql']; /** * Return an ISO-8601 formatted timestamp to use in the API response. From 09832cc558d4c6abbd0d4f06417a1bf5af5b3f84 Mon Sep 17 00:00:00 2001 From: DaneEveritt Date: Sun, 29 May 2022 17:07:54 -0400 Subject: [PATCH 064/458] Ensure we can properly create an activity log entry; always return soft-deleted models --- app/Events/ActivityLogged.php | 37 +++++++++++++++++++ app/Models/ActivityLog.php | 31 ++++++++++++++-- app/Models/ActivityLogSubject.php | 2 +- app/Services/Activity/ActivityLogService.php | 29 +++++++++------ .../Client/Server/Backup/DeleteBackupTest.php | 34 +++++++++++------ 5 files changed, 105 insertions(+), 28 deletions(-) create mode 100644 app/Events/ActivityLogged.php diff --git a/app/Events/ActivityLogged.php b/app/Events/ActivityLogged.php new file mode 100644 index 0000000000..2542887608 --- /dev/null +++ b/app/Events/ActivityLogged.php @@ -0,0 +1,37 @@ +model = $model; + } + + public function is(string $event): bool + { + return $this->model->event === $event; + } + + public function actor(): ?Model + { + return $this->isSystem() ? null : $this->model->actor; + } + + public function isServerEvent() + { + return Str::startsWith($this->model->event, 'server:'); + } + + public function isSystem() + { + return is_null($this->model->actor_id); + } +} diff --git a/app/Models/ActivityLog.php b/app/Models/ActivityLog.php index c8be7249ec..0681b21ca0 100644 --- a/app/Models/ActivityLog.php +++ b/app/Models/ActivityLog.php @@ -2,6 +2,8 @@ namespace Pterodactyl\Models; +use Illuminate\Support\Facades\Event; +use Pterodactyl\Events\ActivityLogged; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Relations\MorphTo; use Illuminate\Database\Eloquent\Model as IlluminateModel; @@ -16,13 +18,14 @@ * @property string|null $description * @property string|null $actor_type * @property int|null $actor_id - * @property \Illuminate\Support\Collection $properties + * @property \Illuminate\Support\Collection|null $properties * @property string $timestamp * @property IlluminateModel|\Eloquent $actor - * @property IlluminateModel|\Eloquent $subject + * @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\ActivityLogSubject[] $subjects + * @property int|null $subjects_count * - * @method static Builder|ActivityLog forEvent(string $event) * @method static Builder|ActivityLog forActor(\Illuminate\Database\Eloquent\Model $actor) + * @method static Builder|ActivityLog forEvent(string $action) * @method static Builder|ActivityLog newModelQuery() * @method static Builder|ActivityLog newQuery() * @method static Builder|ActivityLog query() @@ -50,6 +53,8 @@ class ActivityLog extends Model 'properties' => 'collection', ]; + protected $with = ['subjects']; + public static $validationRules = [ 'event' => ['required', 'string'], 'batch' => ['nullable', 'uuid'], @@ -60,7 +65,12 @@ class ActivityLog extends Model public function actor(): MorphTo { - return $this->morphTo(); + return $this->morphTo()->withTrashed(); + } + + public function subjects() + { + return $this->hasMany(ActivityLogSubject::class); } public function scopeForEvent(Builder $builder, string $action): Builder @@ -75,4 +85,17 @@ public function scopeForActor(Builder $builder, IlluminateModel $actor): Builder { return $builder->whereMorphedTo('actor', $actor); } + + /** + * Boots the model event listeners. This will trigger an activity log event every + * time a new model is inserted which can then be captured and worked with as needed. + */ + protected static function boot() + { + parent::boot(); + + static::created(function (self $model) { + Event::dispatch(new ActivityLogged($model)); + }); + } } diff --git a/app/Models/ActivityLogSubject.php b/app/Models/ActivityLogSubject.php index 47264dbd61..b1262e392f 100644 --- a/app/Models/ActivityLogSubject.php +++ b/app/Models/ActivityLogSubject.php @@ -35,6 +35,6 @@ public function activityLog() public function subject() { - return $this->morphTo(); + return $this->morphTo()->withTrashed(); } } diff --git a/app/Services/Activity/ActivityLogService.php b/app/Services/Activity/ActivityLogService.php index 7c8227d2e9..41011e8368 100644 --- a/app/Services/Activity/ActivityLogService.php +++ b/app/Services/Activity/ActivityLogService.php @@ -6,6 +6,7 @@ use Webmozart\Assert\Assert; use Illuminate\Support\Collection; use Pterodactyl\Models\ActivityLog; +use Illuminate\Support\Facades\Log; use Illuminate\Contracts\Auth\Factory; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Facades\Request; @@ -132,7 +133,9 @@ public function withRequestMetadata(): self /** * Logs an activity log entry with the set values and then returns the - * model instance to the caller. + * model instance to the caller. If there is an exception encountered while + * performing this action it will be logged to the disk but will not interrupt + * the code flow. */ public function log(string $description = null): ActivityLog { @@ -142,7 +145,13 @@ public function log(string $description = null): ActivityLog $activity->description = $description; } - return $this->save(); + try { + return $this->save(); + } catch (\Throwable|\Exception $exception) { + Log::error($exception); + } + + return $activity; } /** @@ -166,9 +175,9 @@ public function clone(): self public function transaction(\Closure $callback) { return $this->connection->transaction(function () use ($callback) { - $response = $callback($activity = $this->getActivity()); + $response = $callback($this); - $this->save($activity); + $this->save(); return $response; }); @@ -209,14 +218,12 @@ protected function getActivity(): ActivityLog * * @throws \Throwable */ - protected function save(ActivityLog $activity = null): ActivityLog + protected function save(): ActivityLog { - $activity = $activity ?? $this->activity; + Assert::notNull($this->activity); - Assert::notNull($activity); - - $response = $this->connection->transaction(function () use ($activity) { - $activity->save(); + $response = $this->connection->transaction(function () { + $this->activity->save(); $subjects = Collection::make($this->subjects) ->map(fn (Model $subject) => [ @@ -229,7 +236,7 @@ protected function save(ActivityLog $activity = null): ActivityLog ActivityLogSubject::insert($subjects); - return $activity; + return $this->activity; }); $this->activity = null; diff --git a/tests/Integration/Api/Client/Server/Backup/DeleteBackupTest.php b/tests/Integration/Api/Client/Server/Backup/DeleteBackupTest.php index 0fc80610da..05b709efa9 100644 --- a/tests/Integration/Api/Client/Server/Backup/DeleteBackupTest.php +++ b/tests/Integration/Api/Client/Server/Backup/DeleteBackupTest.php @@ -5,8 +5,9 @@ use Mockery; use Illuminate\Http\Response; use Pterodactyl\Models\Backup; -use Pterodactyl\Models\AuditLog; use Pterodactyl\Models\Permission; +use Illuminate\Support\Facades\Event; +use Pterodactyl\Events\ActivityLogged; use Pterodactyl\Repositories\Wings\DaemonBackupRepository; use Pterodactyl\Tests\Integration\Api\Client\ClientApiIntegrationTestCase; @@ -34,32 +35,41 @@ public function testUserWithoutPermissionCannotDeleteBackup() /** * Tests that a backup can be deleted for a server and that it is properly updated * in the database. Once deleted there should also be a corresponding record in the - * audit logs table for this API call. + * activity logs table for this API call. */ public function testBackupCanBeDeleted() { + Event::fake([ActivityLogged::class]); + [$user, $server] = $this->generateTestAccount([Permission::ACTION_BACKUP_DELETE]); /** @var \Pterodactyl\Models\Backup $backup */ $backup = Backup::factory()->create(['server_id' => $server->id]); - $this->repository->expects('setServer->delete')->with(Mockery::on(function ($value) use ($backup) { - return $value instanceof Backup && $value->uuid === $backup->uuid; - }))->andReturn(new Response()); + $this->repository->expects('setServer->delete')->with( + Mockery::on(function ($value) use ($backup) { + return $value instanceof Backup && $value->uuid === $backup->uuid; + }) + )->andReturn(new Response()); $this->actingAs($user)->deleteJson($this->link($backup))->assertStatus(Response::HTTP_NO_CONTENT); $backup->refresh(); + $this->assertSoftDeleted($backup); - $this->assertNotNull($backup->deleted_at); + Event::assertDispatched(ActivityLogged::class, function (ActivityLogged $event) use ($backup, $user) { + $this->assertTrue($event->isServerEvent()); + $this->assertTrue($event->is('server:backup.delete')); + $this->assertTrue($user->is($event->actor())); + $this->assertCount(2, $event->model->subjects); - $this->actingAs($user)->deleteJson($this->link($backup))->assertStatus(Response::HTTP_NOT_FOUND); + $subjects = $event->model->subjects; + $this->assertCount(1, $subjects->filter(fn ($model) => $model->subject->is($backup))); + $this->assertCount(1, $subjects->filter(fn ($model) => $model->subject->is($backup->server))); - $event = $backup->audits()->where('action', AuditLog::SERVER__BACKUP_DELETED)->latest()->first(); + return true; + }); - $this->assertNotNull($event); - $this->assertFalse($event->is_system); - $this->assertEquals($backup->server_id, $event->server_id); - $this->assertEquals($user->id, $event->user_id); + $this->actingAs($user)->deleteJson($this->link($backup))->assertStatus(Response::HTTP_NOT_FOUND); } } From 0621d8475d6d702a4892dac1b9a8228b46a41ae4 Mon Sep 17 00:00:00 2001 From: DaneEveritt Date: Sun, 29 May 2022 17:52:14 -0400 Subject: [PATCH 065/458] Return tests to passing now that we don't ignore a critical event... --- app/Models/Model.php | 48 ++++++++----------- .../Activity/ActivityLogTargetableService.php | 9 ---- database/Factories/ServerFactory.php | 4 +- database/Factories/TaskFactory.php | 10 +--- database/Factories/UserFactory.php | 5 +- .../Users/ExternalUserControllerTest.php | 5 +- .../Api/Client/AccountControllerTest.php | 5 +- .../Schedule/DeleteServerScheduleTest.php | 2 +- .../Schedule/GetServerSchedulesTest.php | 2 +- .../Schedule/UpdateServerScheduleTest.php | 2 +- .../CreateServerScheduleTaskTest.php | 2 +- .../Server/Subuser/DeleteSubuserTest.php | 3 +- tests/Integration/TestResponse.php | 6 ++- .../Traits/Integration/CreatesTestModels.php | 1 - 14 files changed, 44 insertions(+), 60 deletions(-) diff --git a/app/Models/Model.php b/app/Models/Model.php index 23cf5e7e97..cc8ace6c37 100644 --- a/app/Models/Model.php +++ b/app/Models/Model.php @@ -7,6 +7,7 @@ use Illuminate\Validation\Rule; use Illuminate\Container\Container; use Illuminate\Contracts\Validation\Factory; +use Illuminate\Validation\ValidationException; use Illuminate\Database\Eloquent\Factories\HasFactory; use Pterodactyl\Exceptions\Model\DataValidationException; use Illuminate\Database\Eloquent\Model as IlluminateModel; @@ -30,13 +31,6 @@ abstract class Model extends IlluminateModel */ protected $skipValidation = false; - /** - * The validator instance used by this model. - * - * @var \Illuminate\Validation\Validator - */ - protected $validator; - /** * @var \Illuminate\Contracts\Validation\Factory */ @@ -60,8 +54,10 @@ protected static function boot() static::$validatorFactory = Container::getInstance()->make(Factory::class); static::saving(function (Model $model) { - if (!$model->validate()) { - throw new DataValidationException($model->getValidator()); + try { + $model->validate(); + } catch (ValidationException $exception) { + throw new DataValidationException($exception->validator); } return true; @@ -101,14 +97,9 @@ public function skipValidation() */ public function getValidator() { - $rules = $this->getKey() ? static::getRulesForUpdate($this) : static::getRules(); + $rules = $this->exists ? static::getRulesForUpdate($this) : static::getRules(); - return $this->validator ?: $this->validator = static::$validatorFactory->make( - [], - $rules, - [], - [] - ); + return static::$validatorFactory->make([], $rules, [], []); } /** @@ -139,14 +130,14 @@ public static function getRulesForField(string $field): array * Returns the rules associated with the model, specifically for updating the given model * rather than just creating it. * - * @param \Illuminate\Database\Eloquent\Model|int|string $id + * @param \Illuminate\Database\Eloquent\Model|int|string $model * * @return array */ - public static function getRulesForUpdate($id, string $primaryKey = 'id') + public static function getRulesForUpdate($model, string $column = 'id') { - if ($id instanceof Model) { - [$primaryKey, $id] = [$id->getKeyName(), $id->getKey()]; + if ($model instanceof Model) { + [$id, $column] = [$model->getKey(), $model->getKeyName()]; } $rules = static::getRules(); @@ -163,7 +154,7 @@ public static function getRulesForUpdate($id, string $primaryKey = 'id') [, $args] = explode(':', $datum); $args = explode(',', $args); - $datum = Rule::unique($args[0], $args[1] ?? $key)->ignore($id, $primaryKey)->__toString(); + $datum = Rule::unique($args[0], $args[1] ?? $key)->ignore($id ?? $model, $column); } } @@ -172,16 +163,15 @@ public static function getRulesForUpdate($id, string $primaryKey = 'id') /** * Determines if the model is in a valid state or not. - * - * @return bool */ - public function validate() + public function validate(): void { if ($this->skipValidation) { - return true; + return; } - return $this->getValidator()->setData( + $validator = $this->getValidator(); + $validator->setData( // Trying to do self::toArray() here will leave out keys based on the whitelist/blacklist // for that model. Doing this will return all of the attributes in a format that can // properly be validated. @@ -189,7 +179,11 @@ public function validate() $this->getAttributes(), $this->getMutatedAttributes() ) - )->passes(); + ); + + if (!$validator->passes()) { + throw new ValidationException($validator); + } } /** diff --git a/app/Services/Activity/ActivityLogTargetableService.php b/app/Services/Activity/ActivityLogTargetableService.php index 6883a4ce15..a4da5b5f3b 100644 --- a/app/Services/Activity/ActivityLogTargetableService.php +++ b/app/Services/Activity/ActivityLogTargetableService.php @@ -2,7 +2,6 @@ namespace Pterodactyl\Services\Activity; -use InvalidArgumentException; use Illuminate\Database\Eloquent\Model; class ActivityLogTargetableService @@ -13,19 +12,11 @@ class ActivityLogTargetableService public function setActor(Model $actor): void { - if (!is_null($this->actor)) { - throw new InvalidArgumentException('Cannot call ' . __METHOD__ . ' when an actor is already set on the instance.'); - } - $this->actor = $actor; } public function setSubject(Model $subject): void { - if (!is_null($this->subject)) { - throw new InvalidArgumentException('Cannot call ' . __METHOD__ . ' when a target is already set on the instance.'); - } - $this->subject = $subject; } diff --git a/database/Factories/ServerFactory.php b/database/Factories/ServerFactory.php index b9145c679d..d89bd6a561 100644 --- a/database/Factories/ServerFactory.php +++ b/database/Factories/ServerFactory.php @@ -40,8 +40,8 @@ public function definition() 'oom_disabled' => 0, 'startup' => '/bin/bash echo "hello world"', 'image' => 'foo/bar:latest', - 'allocation_limit' => 0, - 'database_limit' => 0, + 'allocation_limit' => null, + 'database_limit' => null, 'backup_limit' => 0, 'created_at' => Carbon::now(), 'updated_at' => Carbon::now(), diff --git a/database/Factories/TaskFactory.php b/database/Factories/TaskFactory.php index f04ebbe8b3..32bf950cca 100644 --- a/database/Factories/TaskFactory.php +++ b/database/Factories/TaskFactory.php @@ -2,25 +2,17 @@ namespace Database\Factories; -use Pterodactyl\Models\Task; use Illuminate\Database\Eloquent\Factories\Factory; class TaskFactory extends Factory { - /** - * The name of the factory's corresponding model. - * - * @var string - */ - protected $model = Task::class; - /** * Define the model's default state. */ public function definition(): array { return [ - 'sequence_id' => $this->faker->randomNumber(1), + 'sequence_id' => $this->faker->numberBetween(1, 10), 'action' => 'command', 'payload' => 'test command', 'time_offset' => 120, diff --git a/database/Factories/UserFactory.php b/database/Factories/UserFactory.php index 8eae7c55d0..cabfed7be9 100644 --- a/database/Factories/UserFactory.php +++ b/database/Factories/UserFactory.php @@ -4,6 +4,7 @@ use Carbon\Carbon; use Ramsey\Uuid\Uuid; +use Illuminate\Support\Str; use Pterodactyl\Models\User; use Illuminate\Database\Eloquent\Factories\Factory; @@ -24,10 +25,10 @@ public function definition(): array static $password; return [ - 'external_id' => $this->faker->unique()->isbn10, + 'external_id' => null, 'uuid' => Uuid::uuid4()->toString(), 'username' => $this->faker->unique()->userName, - 'email' => $this->faker->unique()->safeEmail, + 'email' => Str::random(32) . '@example.com', 'name_first' => $this->faker->firstName, 'name_last' => $this->faker->lastName, 'password' => $password ?: $password = bcrypt('password'), diff --git a/tests/Integration/Api/Application/Users/ExternalUserControllerTest.php b/tests/Integration/Api/Application/Users/ExternalUserControllerTest.php index 74e03df544..fc37b72329 100644 --- a/tests/Integration/Api/Application/Users/ExternalUserControllerTest.php +++ b/tests/Integration/Api/Application/Users/ExternalUserControllerTest.php @@ -2,6 +2,7 @@ namespace Pterodactyl\Tests\Integration\Api\Application\Users; +use Illuminate\Support\Str; use Pterodactyl\Models\User; use Illuminate\Http\Response; use Pterodactyl\Tests\Integration\Api\Application\ApplicationApiIntegrationTestCase; @@ -13,7 +14,7 @@ class ExternalUserControllerTest extends ApplicationApiIntegrationTestCase */ public function testGetRemoteUser() { - $user = User::factory()->create(); + $user = User::factory()->create(['external_id' => Str::random()]); $response = $this->getJson('/api/application/users/external/' . $user->external_id); $response->assertStatus(Response::HTTP_OK); @@ -60,7 +61,7 @@ public function testGetMissingUser() */ public function testErrorReturnedIfNoPermission() { - $user = User::factory()->create(); + $user = User::factory()->create(['external_id' => Str::random()]); $this->createNewDefaultApiKey($this->getApiUser(), ['r_users' => 0]); $response = $this->getJson('/api/application/users/external/' . $user->external_id); diff --git a/tests/Integration/Api/Client/AccountControllerTest.php b/tests/Integration/Api/Client/AccountControllerTest.php index 971b834210..6505f43f18 100644 --- a/tests/Integration/Api/Client/AccountControllerTest.php +++ b/tests/Integration/Api/Client/AccountControllerTest.php @@ -2,6 +2,7 @@ namespace Pterodactyl\Tests\Integration\Api\Client; +use Illuminate\Support\Str; use Pterodactyl\Models\User; use Illuminate\Http\Response; use Illuminate\Support\Facades\Hash; @@ -41,13 +42,13 @@ public function testEmailIsUpdated() $user = User::factory()->create(); $response = $this->actingAs($user)->putJson('/api/client/account/email', [ - 'email' => 'hodor@example.com', + 'email' => $email = Str::random() . '@example.com', 'password' => 'password', ]); $response->assertStatus(Response::HTTP_NO_CONTENT); - $this->assertDatabaseHas('users', ['id' => $user->id, 'email' => 'hodor@example.com']); + $this->assertDatabaseHas('users', ['id' => $user->id, 'email' => $email]); } /** diff --git a/tests/Integration/Api/Client/Server/Schedule/DeleteServerScheduleTest.php b/tests/Integration/Api/Client/Server/Schedule/DeleteServerScheduleTest.php index 25f2741159..f5a2e49dd2 100644 --- a/tests/Integration/Api/Client/Server/Schedule/DeleteServerScheduleTest.php +++ b/tests/Integration/Api/Client/Server/Schedule/DeleteServerScheduleTest.php @@ -50,7 +50,7 @@ public function testNotFoundErrorIsReturnedIfScheduleDoesNotExistAtAll() public function testNotFoundErrorIsReturnedIfScheduleDoesNotBelongToServer() { [$user, $server] = $this->generateTestAccount(); - [, $server2] = $this->generateTestAccount(['user_id' => $user->id]); + $server2 = $this->createServerModel(['owner_id' => $user->id]); $schedule = Schedule::factory()->create(['server_id' => $server2->id]); diff --git a/tests/Integration/Api/Client/Server/Schedule/GetServerSchedulesTest.php b/tests/Integration/Api/Client/Server/Schedule/GetServerSchedulesTest.php index 57c282ee76..34830a421a 100644 --- a/tests/Integration/Api/Client/Server/Schedule/GetServerSchedulesTest.php +++ b/tests/Integration/Api/Client/Server/Schedule/GetServerSchedulesTest.php @@ -64,7 +64,7 @@ public function testServerSchedulesAreReturned($permissions, $individual) public function testScheduleBelongingToAnotherServerCannotBeViewed() { [$user, $server] = $this->generateTestAccount(); - [, $server2] = $this->generateTestAccount(['user_id' => $user->id]); + $server2 = $this->createServerModel(['owner_id' => $user->id]); $schedule = Schedule::factory()->create(['server_id' => $server2->id]); diff --git a/tests/Integration/Api/Client/Server/Schedule/UpdateServerScheduleTest.php b/tests/Integration/Api/Client/Server/Schedule/UpdateServerScheduleTest.php index e99486d4ef..f095dd88ff 100644 --- a/tests/Integration/Api/Client/Server/Schedule/UpdateServerScheduleTest.php +++ b/tests/Integration/Api/Client/Server/Schedule/UpdateServerScheduleTest.php @@ -58,7 +58,7 @@ public function testScheduleCanBeUpdated($permissions) public function testErrorIsReturnedIfScheduleDoesNotBelongToServer() { [$user, $server] = $this->generateTestAccount(); - [, $server2] = $this->generateTestAccount(['user_id' => $user->id]); + $server2 = $this->createServerModel(['owner_id' => $user->id]); $schedule = Schedule::factory()->create(['server_id' => $server2->id]); diff --git a/tests/Integration/Api/Client/Server/ScheduleTask/CreateServerScheduleTaskTest.php b/tests/Integration/Api/Client/Server/ScheduleTask/CreateServerScheduleTaskTest.php index b031b4fb2b..81c65b1d2f 100644 --- a/tests/Integration/Api/Client/Server/ScheduleTask/CreateServerScheduleTaskTest.php +++ b/tests/Integration/Api/Client/Server/ScheduleTask/CreateServerScheduleTaskTest.php @@ -145,7 +145,7 @@ public function testErrorIsReturnedIfTooManyTasksExistForSchedule() public function testErrorIsReturnedIfScheduleDoesNotBelongToServer() { [$user, $server] = $this->generateTestAccount(); - [, $server2] = $this->generateTestAccount(['user_id' => $user->id]); + $server2 = $this->createServerModel(['owner_id' => $user->id]); /** @var \Pterodactyl\Models\Schedule $schedule */ $schedule = Schedule::factory()->create(['server_id' => $server2->id]); diff --git a/tests/Integration/Api/Client/Server/Subuser/DeleteSubuserTest.php b/tests/Integration/Api/Client/Server/Subuser/DeleteSubuserTest.php index eeb2485103..fbcbbc01c7 100644 --- a/tests/Integration/Api/Client/Server/Subuser/DeleteSubuserTest.php +++ b/tests/Integration/Api/Client/Server/Subuser/DeleteSubuserTest.php @@ -32,8 +32,9 @@ public function testCorrectSubuserIsDeletedFromServer() /** @var \Pterodactyl\Models\User $differentUser */ $differentUser = User::factory()->create(); + $real = Uuid::uuid4()->toString(); // Generate a UUID that lines up with a user in the database if it were to be cast to an int. - $uuid = $differentUser->id . str_repeat('a', strlen((string) $differentUser->id)) . substr(Uuid::uuid4()->toString(), 8); + $uuid = $differentUser->id . substr($real, strlen((string) $differentUser->id)); /** @var \Pterodactyl\Models\User $subuser */ $subuser = User::factory()->create(['uuid' => $uuid]); diff --git a/tests/Integration/TestResponse.php b/tests/Integration/TestResponse.php index 9553f432a9..e715071e95 100644 --- a/tests/Integration/TestResponse.php +++ b/tests/Integration/TestResponse.php @@ -28,7 +28,11 @@ public function assertStatus($status) if ($actual !== $status && $status !== 500) { $this->dump(); if (!is_null($this->exception) && !$this->exception instanceof DisplayException && !$this->exception instanceof ValidationException) { - dump($this->exception); + dump([ + 'exception_class' => get_class($this->exception), + 'message' => $this->exception->getMessage(), + 'trace' => $this->exception->getTrace(), + ]); } } diff --git a/tests/Traits/Integration/CreatesTestModels.php b/tests/Traits/Integration/CreatesTestModels.php index 51e4b76aab..ce64376ce5 100644 --- a/tests/Traits/Integration/CreatesTestModels.php +++ b/tests/Traits/Integration/CreatesTestModels.php @@ -94,7 +94,6 @@ public function generateTestAccount(array $permissions = []): array return [$user, $this->createServerModel(['user_id' => $user->id])]; } - /** @var \Pterodactyl\Models\Server $server */ $server = $this->createServerModel(); Subuser::query()->create([ From 0b2c0db17083f8a8c6d7ca58078c5f385072b701 Mon Sep 17 00:00:00 2001 From: DaneEveritt Date: Sun, 29 May 2022 18:20:54 -0400 Subject: [PATCH 066/458] Remove last references to audit logs --- .../Servers/ServerDetailsController.php | 79 +++++++++---------- app/Models/AuditLog.php | 33 +------- app/Models/Backup.php | 10 --- app/Models/Server.php | 4 +- 4 files changed, 43 insertions(+), 83 deletions(-) diff --git a/app/Http/Controllers/Api/Remote/Servers/ServerDetailsController.php b/app/Http/Controllers/Api/Remote/Servers/ServerDetailsController.php index 0e93c60cac..3ed27de90a 100644 --- a/app/Http/Controllers/Api/Remote/Servers/ServerDetailsController.php +++ b/app/Http/Controllers/Api/Remote/Servers/ServerDetailsController.php @@ -4,14 +4,10 @@ use Illuminate\Http\Request; use Pterodactyl\Models\Server; -use Pterodactyl\Models\Backup; -use Pterodactyl\Models\AuditLog; use Illuminate\Http\JsonResponse; use Pterodactyl\Facades\Activity; -use Illuminate\Database\Query\Builder; -use Illuminate\Database\Query\JoinClause; use Pterodactyl\Http\Controllers\Controller; -use Pterodactyl\Repositories\Eloquent\NodeRepository; +use Illuminate\Database\ConnectionInterface; use Pterodactyl\Services\Eggs\EggConfigurationService; use Pterodactyl\Repositories\Eloquent\ServerRepository; use Pterodactyl\Http\Resources\Wings\ServerConfigurationCollection; @@ -19,6 +15,11 @@ class ServerDetailsController extends Controller { + /** + * @var \Illuminate\Database\ConnectionInterface + */ + protected ConnectionInterface $connection; + /** * @var \Pterodactyl\Services\Eggs\EggConfigurationService */ @@ -38,14 +39,15 @@ class ServerDetailsController extends Controller * ServerConfigurationController constructor. */ public function __construct( + ConnectionInterface $connection, ServerRepository $repository, ServerConfigurationStructureService $configurationStructureService, - EggConfigurationService $eggConfigurationService, - NodeRepository $nodeRepository + EggConfigurationService $eggConfigurationService ) { $this->eggConfigurationService = $eggConfigurationService; $this->repository = $repository; $this->configurationStructureService = $configurationStructureService; + $this->connection = $connection; } /** @@ -110,45 +112,38 @@ public function resetState(Request $request) // For each of those servers we'll track a new audit log entry to mark them as // failed and then update them all to be in a valid state. $servers = Server::query() - ->select('servers.*') - ->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) - ->orderByDesc('created_at') - ->limit(1); - }, 'started', 'started.server_id', '=', 'servers.id') - ->leftJoin('audit_logs as completed', function (JoinClause $clause) { - $clause->whereColumn('completed.created_at', '>', 'started.created_at') - ->whereIn('completed.action', [ - AuditLog::SERVER__BACKUP_RESTORE_COMPLETED, - AuditLog::SERVER__BACKUP_RESTORE_FAILED, - ]); - }) - ->whereNotNull('started.id') - ->whereNull('completed.id') - ->where('servers.node_id', $node->id) - ->where('servers.status', Server::STATUS_RESTORING_BACKUP) + ->with([ + 'activity' => fn ($builder) => $builder + ->where('activity_logs.event', 'server:backup.restore-started') + ->latest('timestamp'), + ]) + ->where('node_id', $node->id) + ->where('status', Server::STATUS_RESTORING_BACKUP) ->get(); - $backups = Backup::query()->whereIn('uuid', $servers->pluck('backup_uuid'))->get(); - - /** @var \Pterodactyl\Models\Server $server */ - foreach ($servers as $server) { - $server->update(['status' => null]); - - if ($backup = $backups->where('uuid', $server->getAttribute('backup_uuid'))->first()) { - // Just create a new audit entry for this event and update the server state - // so that power actions, file management, and backups can resume as normal. - Activity::event('server:backup.restore-failed')->subject($server, $backup)->log(); + $this->connection->transaction(function () use ($node, $servers) { + /** @var \Pterodactyl\Models\Server $server */ + foreach ($servers as $server) { + /** @var \Pterodactyl\Models\ActivityLog|null $activity */ + $activity = $server->activity->first(); + if (!is_null($activity)) { + if ($subject = $activity->subjects->where('subject_type', 'backup')->first()) { + // Just create a new audit entry for this event and update the server state + // so that power actions, file management, and backups can resume as normal. + Activity::event('server:backup.restore-failed') + ->subject($server, $subject->subject) + ->property('name', $subject->subject->name) + ->log(); + } + } } - } - // Update any server marked as installing or restoring as being in a normal state - // at this point in the process. - Server::query()->where('node_id', $node->id) - ->whereIn('status', [Server::STATUS_INSTALLING, Server::STATUS_RESTORING_BACKUP]) - ->update(['status' => null]); + // Update any server marked as installing or restoring as being in a normal state + // at this point in the process. + Server::query()->where('node_id', $node->id) + ->whereIn('status', [Server::STATUS_INSTALLING, Server::STATUS_RESTORING_BACKUP]) + ->update(['status' => null]); + }); return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT); } diff --git a/app/Models/AuditLog.php b/app/Models/AuditLog.php index eb306da9c1..6dea11f3cc 100644 --- a/app/Models/AuditLog.php +++ b/app/Models/AuditLog.php @@ -7,41 +7,12 @@ 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 \Pterodactyl\Models\Server|null $server + * @deprecated — this class will be dropped in a future version, use the activity log */ class AuditLog extends Model { public const UPDATED_AT = null; - public const SERVER__FILESYSTEM_DOWNLOAD = 'server:filesystem.download'; - public const SERVER__FILESYSTEM_WRITE = 'server:filesystem.write'; - public const SERVER__FILESYSTEM_DELETE = 'server:filesystem.delete'; - public const SERVER__FILESYSTEM_RENAME = 'server:filesystem.rename'; - public const SERVER__FILESYSTEM_COMPRESS = 'server:filesystem.compress'; - public const SERVER__FILESYSTEM_DECOMPRESS = 'server:filesystem.decompress'; - public const SERVER__FILESYSTEM_PULL = 'server:filesystem.pull'; - public const SERVER__BACKUP_STARTED = 'server:backup.started'; - public const SERVER__BACKUP_FAILED = 'server:backup.failed'; - 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'; - /** * @var string[] */ @@ -104,6 +75,8 @@ public function server() * you can always make modifications to it as needed before saving. * * @return $this + * + * @deprecated */ public static function instance(string $action, array $metadata, bool $isSystem = false) { diff --git a/app/Models/Backup.php b/app/Models/Backup.php index f608edc7ae..84ba680a21 100644 --- a/app/Models/Backup.php +++ b/app/Models/Backup.php @@ -99,14 +99,4 @@ public function server() { return $this->belongsTo(Server::class); } - - /** - * @return \Illuminate\Database\Eloquent\Relations\HasMany - */ - public function audits() - { - return $this->hasMany(AuditLog::class, 'metadata->backup_uuid', 'uuid') - ->where('action', 'LIKE', 'server:backup.%'); - // ->where('metadata->backup_uuid', $this->uuid); - } } diff --git a/app/Models/Server.php b/app/Models/Server.php index 7fe28674a4..3f67c396dc 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -9,7 +9,7 @@ use Pterodactyl\Exceptions\Http\Server\ServerStateConflictException; /** - * Pterodactyl\Models\Server. + * \Pterodactyl\Models\Server. * * @property int $id * @property string|null $external_id @@ -38,6 +38,8 @@ * @property int $backup_limit * @property \Illuminate\Support\Carbon|null $created_at * @property \Illuminate\Support\Carbon|null $updated_at + * @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\ActivityLog[] $activity + * @property int|null $activity_count * @property \Pterodactyl\Models\Allocation|null $allocation * @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\Allocation[] $allocations * @property int|null $allocations_count From 287fd608916bcf9c830c843ef45b978cfdb2903c Mon Sep 17 00:00:00 2001 From: DaneEveritt Date: Sun, 29 May 2022 18:48:35 -0400 Subject: [PATCH 067/458] Log activity when modifying account details --- .../Api/Client/AccountController.php | 11 +++- .../Api/Client/ApiKeyController.php | 57 +++++-------------- .../Api/Client/SSHKeyController.php | 15 ++++- .../Api/Client/TwoFactorController.php | 5 ++ .../Remote/Backups/BackupStatusController.php | 2 +- app/Http/Controllers/Auth/LoginController.php | 2 +- .../Middleware/AccountActivitySubject.php | 22 +++++++ ...vityLogs.php => ServerActivitySubject.php} | 2 +- app/Listeners/Auth/AuthenticationListener.php | 2 +- app/Listeners/Auth/PasswordResetListener.php | 2 +- app/Listeners/Auth/TwoFactorListener.php | 2 +- app/Models/User.php | 2 +- app/Providers/AppServiceProvider.php | 4 ++ app/Services/Activity/ActivityLogService.php | 7 ++- routes/api-client.php | 7 ++- 15 files changed, 85 insertions(+), 57 deletions(-) create mode 100644 app/Http/Middleware/AccountActivitySubject.php rename app/Http/Middleware/{ServerActivityLogs.php => ServerActivitySubject.php} (96%) diff --git a/app/Http/Controllers/Api/Client/AccountController.php b/app/Http/Controllers/Api/Client/AccountController.php index 963c01374f..9551bf690e 100644 --- a/app/Http/Controllers/Api/Client/AccountController.php +++ b/app/Http/Controllers/Api/Client/AccountController.php @@ -6,6 +6,7 @@ use Illuminate\Http\Response; use Illuminate\Auth\AuthManager; use Illuminate\Http\JsonResponse; +use Pterodactyl\Facades\Activity; use Pterodactyl\Services\Users\UserUpdateService; use Pterodactyl\Transformers\Api\Client\AccountTransformer; use Pterodactyl\Http\Requests\Api\Client\Account\UpdateEmailRequest; @@ -43,14 +44,16 @@ public function index(Request $request): array /** * Update the authenticated user's email address. - * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ public function updateEmail(UpdateEmailRequest $request): JsonResponse { + $original = $request->user()->email; $this->updateService->handle($request->user(), $request->validated()); + Activity::event('user:account.email-changed') + ->property(['old' => $original, 'new' => $request->input('email')]) + ->log(); + return new JsonResponse([], Response::HTTP_NO_CONTENT); } @@ -76,6 +79,8 @@ public function updatePassword(UpdatePasswordRequest $request): JsonResponse $guard->logoutOtherDevices($request->input('password')); } + Activity::event('user:account.password-changed')->log(); + return new JsonResponse([], Response::HTTP_NO_CONTENT); } } diff --git a/app/Http/Controllers/Api/Client/ApiKeyController.php b/app/Http/Controllers/Api/Client/ApiKeyController.php index 427c353a51..5e888e91df 100644 --- a/app/Http/Controllers/Api/Client/ApiKeyController.php +++ b/app/Http/Controllers/Api/Client/ApiKeyController.php @@ -4,47 +4,14 @@ use Pterodactyl\Models\ApiKey; use Illuminate\Http\JsonResponse; +use Pterodactyl\Facades\Activity; use Pterodactyl\Exceptions\DisplayException; -use Illuminate\Contracts\Encryption\Encrypter; -use Pterodactyl\Services\Api\KeyCreationService; -use Pterodactyl\Repositories\Eloquent\ApiKeyRepository; use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest; use Pterodactyl\Transformers\Api\Client\ApiKeyTransformer; -use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Pterodactyl\Http\Requests\Api\Client\Account\StoreApiKeyRequest; class ApiKeyController extends ClientApiController { - /** - * @var \Pterodactyl\Services\Api\KeyCreationService - */ - private $keyCreationService; - - /** - * @var \Illuminate\Contracts\Encryption\Encrypter - */ - private $encrypter; - - /** - * @var \Pterodactyl\Repositories\Eloquent\ApiKeyRepository - */ - private $repository; - - /** - * ApiKeyController constructor. - */ - public function __construct( - Encrypter $encrypter, - KeyCreationService $keyCreationService, - ApiKeyRepository $repository - ) { - parent::__construct(); - - $this->encrypter = $encrypter; - $this->keyCreationService = $keyCreationService; - $this->repository = $repository; - } - /** * Returns all of the API keys that exist for the given client. * @@ -75,6 +42,11 @@ public function store(StoreApiKeyRequest $request) $request->input('allowed_ips') ); + Activity::event('user:api-key.create') + ->subject($token->accessToken) + ->property('identifier', $token->accessToken->identifier) + ->log(); + return $this->fractal->item($token->accessToken) ->transformWith($this->getTransformer(ApiKeyTransformer::class)) ->addMeta(['secret_token' => $token->plainTextToken]) @@ -88,15 +60,16 @@ public function store(StoreApiKeyRequest $request) */ public function delete(ClientApiRequest $request, string $identifier) { - $response = $this->repository->deleteWhere([ - 'key_type' => ApiKey::TYPE_ACCOUNT, - 'user_id' => $request->user()->id, - 'identifier' => $identifier, - ]); + $key = $request->user()->apiKeys() + ->where('key_type', ApiKey::TYPE_ACCOUNT) + ->where('identifier', $identifier) + ->first(); - if (!$response) { - throw new NotFoundHttpException(); - } + Activity::event('user:api-key.delete') + ->property('identifer', $key->identifer) + ->log(); + + $key->delete(); return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT); } diff --git a/app/Http/Controllers/Api/Client/SSHKeyController.php b/app/Http/Controllers/Api/Client/SSHKeyController.php index 80ea6dda7e..6af36827e5 100644 --- a/app/Http/Controllers/Api/Client/SSHKeyController.php +++ b/app/Http/Controllers/Api/Client/SSHKeyController.php @@ -3,6 +3,7 @@ namespace Pterodactyl\Http\Controllers\Api\Client; use Illuminate\Http\JsonResponse; +use Pterodactyl\Facades\Activity; use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest; use Pterodactyl\Transformers\Api\Client\UserSSHKeyTransformer; use Pterodactyl\Http\Requests\Api\Client\Account\StoreSSHKeyRequest; @@ -31,6 +32,11 @@ public function store(StoreSSHKeyRequest $request): array 'fingerprint' => $request->getKeyFingerprint(), ]); + Activity::event('user:ssh-key.create') + ->subject($model) + ->property('fingerprint', $request->getKeyFingerprint()) + ->log(); + return $this->fractal->item($model) ->transformWith($this->getTransformer(UserSSHKeyTransformer::class)) ->toArray(); @@ -41,7 +47,14 @@ public function store(StoreSSHKeyRequest $request): array */ public function delete(ClientApiRequest $request, string $identifier): JsonResponse { - $request->user()->sshKeys()->where('fingerprint', $identifier)->delete(); + $key = $request->user()->sshKeys()->where('fingerprint', $identifier)->firstOrFail(); + + $key->delete(); + + Activity::event('user:ssh-key.delete') + ->subject($key) + ->property('fingerprint', $key->fingerprint) + ->log(); return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT); } diff --git a/app/Http/Controllers/Api/Client/TwoFactorController.php b/app/Http/Controllers/Api/Client/TwoFactorController.php index b14f9d4bc4..40b5479cbb 100644 --- a/app/Http/Controllers/Api/Client/TwoFactorController.php +++ b/app/Http/Controllers/Api/Client/TwoFactorController.php @@ -6,6 +6,7 @@ use Illuminate\Http\Request; use Illuminate\Http\Response; use Illuminate\Http\JsonResponse; +use Pterodactyl\Facades\Activity; use Illuminate\Contracts\Validation\Factory; use Illuminate\Validation\ValidationException; use Pterodactyl\Services\Users\TwoFactorSetupService; @@ -89,6 +90,8 @@ public function store(Request $request) $tokens = $this->toggleTwoFactorService->handle($request->user(), $request->input('code'), true); + Activity::event('user:two-factor.create')->log(); + return new JsonResponse([ 'object' => 'recovery_tokens', 'attributes' => [ @@ -117,6 +120,8 @@ public function delete(Request $request) 'use_totp' => false, ]); + Activity::event('user:two-factor.delete')->log(); + return new JsonResponse([], Response::HTTP_NO_CONTENT); } } diff --git a/app/Http/Controllers/Api/Remote/Backups/BackupStatusController.php b/app/Http/Controllers/Api/Remote/Backups/BackupStatusController.php index 17f62329fd..7b96f1ba77 100644 --- a/app/Http/Controllers/Api/Remote/Backups/BackupStatusController.php +++ b/app/Http/Controllers/Api/Remote/Backups/BackupStatusController.php @@ -45,7 +45,7 @@ public function index(ReportBackupCompleteRequest $request, string $backup) throw new BadRequestHttpException('Cannot update the status of a backup that is already marked as completed.'); } - $action = $request->boolean('successful') ? 'server:backup.complete' : 'server:backup.failed'; + $action = $request->boolean('successful') ? 'server:backup.complete' : 'server:backup.fail'; $log = Activity::event($action)->subject($model, $model->server)->property('name', $model->name); $log->transaction(function () use ($model, $request) { diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php index f26e538495..18cc8815c4 100644 --- a/app/Http/Controllers/Auth/LoginController.php +++ b/app/Http/Controllers/Auth/LoginController.php @@ -72,7 +72,7 @@ public function login(Request $request): JsonResponse return $this->sendLoginResponse($user, $request); } - Activity::event('login.checkpoint')->withRequestMetadata()->subject($user)->log(); + Activity::event('auth:checkpoint')->withRequestMetadata()->subject($user)->log(); $request->session()->put('auth_confirmation_token', [ 'user_id' => $user->id, diff --git a/app/Http/Middleware/AccountActivitySubject.php b/app/Http/Middleware/AccountActivitySubject.php new file mode 100644 index 0000000000..d35a5a6452 --- /dev/null +++ b/app/Http/Middleware/AccountActivitySubject.php @@ -0,0 +1,22 @@ +user()); + LogTarget::setSubject($request->user()); + + return $next($request); + } +} diff --git a/app/Http/Middleware/ServerActivityLogs.php b/app/Http/Middleware/ServerActivitySubject.php similarity index 96% rename from app/Http/Middleware/ServerActivityLogs.php rename to app/Http/Middleware/ServerActivitySubject.php index 6d3c408aaa..344ec0f6e0 100644 --- a/app/Http/Middleware/ServerActivityLogs.php +++ b/app/Http/Middleware/ServerActivitySubject.php @@ -7,7 +7,7 @@ use Pterodactyl\Models\Server; use Pterodactyl\Facades\LogTarget; -class ServerActivityLogs +class ServerActivitySubject { /** * Attempts to automatically scope all of the activity log events registered diff --git a/app/Listeners/Auth/AuthenticationListener.php b/app/Listeners/Auth/AuthenticationListener.php index e19c828ffb..50f6f4d650 100644 --- a/app/Listeners/Auth/AuthenticationListener.php +++ b/app/Listeners/Auth/AuthenticationListener.php @@ -29,7 +29,7 @@ public function handle($event): void } } - $activity->event($event instanceof Failed ? 'login.failed' : 'login.success')->log(); + $activity->event($event instanceof Failed ? 'auth:fail' : 'auth:success')->log(); } public function subscribe(Dispatcher $events): void diff --git a/app/Listeners/Auth/PasswordResetListener.php b/app/Listeners/Auth/PasswordResetListener.php index 54acbc0cf4..7521a689b6 100644 --- a/app/Listeners/Auth/PasswordResetListener.php +++ b/app/Listeners/Auth/PasswordResetListener.php @@ -17,7 +17,7 @@ public function __construct(Request $request) public function handle(PasswordReset $event) { - Activity::event('login.password-reset') + Activity::event('event:password-reset') ->withRequestMetadata() ->subject($event->user) ->log(); diff --git a/app/Listeners/Auth/TwoFactorListener.php b/app/Listeners/Auth/TwoFactorListener.php index 468c5da8d0..b9ab4c19a9 100644 --- a/app/Listeners/Auth/TwoFactorListener.php +++ b/app/Listeners/Auth/TwoFactorListener.php @@ -9,7 +9,7 @@ class TwoFactorListener { public function handle(ProvidedAuthenticationToken $event) { - Activity::event($event->recovery ? 'login.recovery-token' : 'login.token') + Activity::event($event->recovery ? 'auth:recovery-token' : 'auth:token') ->withRequestMetadata() ->subject($event->user) ->log(); diff --git a/app/Models/User.php b/app/Models/User.php index 0570bbf314..b75039819b 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -216,7 +216,7 @@ public function toVueObject(): array */ public function sendPasswordResetNotification($token) { - Activity::event('login.reset-password') + Activity::event('auth:reset-password') ->withRequestMetadata() ->subject($this) ->log('sending password reset email'); diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index a55aa297e8..5b335f9a50 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -8,6 +8,8 @@ use Pterodactyl\Models\User; use Pterodactyl\Models\Server; use Pterodactyl\Models\Backup; +use Pterodactyl\Models\ApiKey; +use Pterodactyl\Models\UserSSHKey; use Illuminate\Support\Facades\URL; use Illuminate\Pagination\Paginator; use Illuminate\Support\Facades\Schema; @@ -39,8 +41,10 @@ public function boot() } Relation::enforceMorphMap([ + 'api_key' => ApiKey::class, 'backup' => Backup::class, 'server' => Server::class, + 'ssh_key' => UserSSHKey::class, 'user' => User::class, ]); } diff --git a/app/Services/Activity/ActivityLogService.php b/app/Services/Activity/ActivityLogService.php index 41011e8368..9726b657fd 100644 --- a/app/Services/Activity/ActivityLogService.php +++ b/app/Services/Activity/ActivityLogService.php @@ -5,8 +5,8 @@ use Illuminate\Support\Arr; use Webmozart\Assert\Assert; use Illuminate\Support\Collection; -use Pterodactyl\Models\ActivityLog; use Illuminate\Support\Facades\Log; +use Pterodactyl\Models\ActivityLog; use Illuminate\Contracts\Auth\Factory; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Facades\Request; @@ -148,6 +148,11 @@ public function log(string $description = null): ActivityLog try { return $this->save(); } catch (\Throwable|\Exception $exception) { + if (config('app.env') !== 'production') { + /* @noinspection PhpUnhandledExceptionInspection */ + throw $exception; + } + Log::error($exception); } diff --git a/routes/api-client.php b/routes/api-client.php index b592ea4203..3099ea0d8c 100644 --- a/routes/api-client.php +++ b/routes/api-client.php @@ -2,7 +2,8 @@ use Illuminate\Support\Facades\Route; use Pterodactyl\Http\Controllers\Api\Client; -use Pterodactyl\Http\Middleware\ServerActivityLogs; +use Pterodactyl\Http\Middleware\ServerActivitySubject; +use Pterodactyl\Http\Middleware\AccountActivitySubject; use Pterodactyl\Http\Middleware\RequireTwoFactorAuthentication; use Pterodactyl\Http\Middleware\Api\Client\Server\ResourceBelongsToServer; use Pterodactyl\Http\Middleware\Api\Client\Server\AuthenticateServerAccess; @@ -18,7 +19,7 @@ Route::get('/', [Client\ClientController::class, 'index'])->name('api:client.index'); Route::get('/permissions', [Client\ClientController::class, 'permissions']); -Route::group(['prefix' => '/account'], function () { +Route::prefix('/account')->middleware(AccountActivitySubject::class)->group(function () { Route::prefix('/')->withoutMiddleware(RequireTwoFactorAuthentication::class)->group(function () { Route::get('/', [Client\AccountController::class, 'index'])->name('api:client.account'); Route::get('/two-factor', [Client\TwoFactorController::class, 'index']); @@ -51,7 +52,7 @@ Route::group([ 'prefix' => '/servers/{server}', 'middleware' => [ - ServerActivityLogs::class, + ServerActivitySubject::class, AuthenticateServerAccess::class, ResourceBelongsToServer::class, ], From 9b7af02690837c500c046c3db699c9c63dffa53d Mon Sep 17 00:00:00 2001 From: DaneEveritt Date: Sun, 29 May 2022 19:26:28 -0400 Subject: [PATCH 068/458] Add activity logging to most of the endpoints --- app/Facades/Activity.php | 1 + .../Api/Client/Servers/DatabaseController.php | 13 ++++ .../Servers/NetworkAllocationController.php | 29 +++++++- .../Api/Client/Servers/ScheduleController.php | 15 +++++ .../Client/Servers/ScheduleTaskController.php | 13 ++++ .../Api/Client/Servers/SettingsController.php | 16 +++++ .../Api/Client/Servers/StartupController.php | 13 ++++ .../Api/Client/Servers/SubuserController.php | 66 ++++++++++++++----- app/Models/Allocation.php | 5 ++ app/Providers/AppServiceProvider.php | 22 ++++--- app/Services/Activity/ActivityLogService.php | 9 +++ 11 files changed, 171 insertions(+), 31 deletions(-) diff --git a/app/Facades/Activity.php b/app/Facades/Activity.php index 86e2bd31c1..febeccd8d7 100644 --- a/app/Facades/Activity.php +++ b/app/Facades/Activity.php @@ -16,6 +16,7 @@ * @method static ActivityLogService property(string|array $key, mixed $value = null) * @method static \Pterodactyl\Models\ActivityLog log(string $description = null) * @method static ActivityLogService clone() + * @method static void reset() * @method static mixed transaction(\Closure $callback) */ class Activity extends Facade diff --git a/app/Http/Controllers/Api/Client/Servers/DatabaseController.php b/app/Http/Controllers/Api/Client/Servers/DatabaseController.php index 4f9aed59d0..dc91866407 100644 --- a/app/Http/Controllers/Api/Client/Servers/DatabaseController.php +++ b/app/Http/Controllers/Api/Client/Servers/DatabaseController.php @@ -5,6 +5,7 @@ use Illuminate\Http\Response; use Pterodactyl\Models\Server; use Pterodactyl\Models\Database; +use Pterodactyl\Facades\Activity; use Pterodactyl\Repositories\Eloquent\DatabaseRepository; use Pterodactyl\Services\Databases\DatabasePasswordService; use Pterodactyl\Transformers\Api\Client\DatabaseTransformer; @@ -76,6 +77,11 @@ public function store(StoreDatabaseRequest $request, Server $server): array { $database = $this->deployDatabaseService->handle($server, $request->validated()); + Activity::event('server:database.create') + ->subject($database) + ->property('name', $database->database) + ->log(); + return $this->fractal->item($database) ->parseIncludes(['password']) ->transformWith($this->getTransformer(DatabaseTransformer::class)) @@ -95,6 +101,8 @@ public function rotatePassword(RotatePasswordRequest $request, Server $server, D $this->passwordService->handle($database); $database->refresh(); + Activity::event('server:database.rotate-password')->subject($database)->log(); + return $this->fractal->item($database) ->parseIncludes(['password']) ->transformWith($this->getTransformer(DatabaseTransformer::class)) @@ -110,6 +118,11 @@ public function delete(DeleteDatabaseRequest $request, Server $server, Database { $this->managementService->delete($database); + Activity::event('server:database.delete') + ->subject($database) + ->property('name', $database->database) + ->log(); + return Response::create('', Response::HTTP_NO_CONTENT); } } diff --git a/app/Http/Controllers/Api/Client/Servers/NetworkAllocationController.php b/app/Http/Controllers/Api/Client/Servers/NetworkAllocationController.php index a24dcab91f..80455e0f81 100644 --- a/app/Http/Controllers/Api/Client/Servers/NetworkAllocationController.php +++ b/app/Http/Controllers/Api/Client/Servers/NetworkAllocationController.php @@ -4,6 +4,7 @@ use Pterodactyl\Models\Server; use Illuminate\Http\JsonResponse; +use Pterodactyl\Facades\Activity; use Pterodactyl\Models\Allocation; use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Repositories\Eloquent\ServerRepository; @@ -68,9 +69,16 @@ public function index(GetNetworkRequest $request, Server $server): array */ public function update(UpdateAllocationRequest $request, Server $server, Allocation $allocation): array { - $allocation = $this->repository->update($allocation->id, [ - 'notes' => $request->input('notes'), - ]); + $original = $allocation->notes; + + $allocation->forceFill(['notes' => $request->input('notes')])->save(); + + if ($original !== $allocation->notes) { + Activity::event('server:allocation.notes') + ->subject($allocation) + ->property(['allocation' => $allocation->toString(), 'old' => $original, 'new' => $allocation->notes]) + ->log(); + } return $this->fractal->item($allocation) ->transformWith($this->getTransformer(AllocationTransformer::class)) @@ -87,6 +95,11 @@ public function setPrimary(SetPrimaryAllocationRequest $request, Server $server, { $this->serverRepository->update($server->id, ['allocation_id' => $allocation->id]); + Activity::event('server:allocation.primary') + ->subject($allocation) + ->property('allocation', $allocation->toString()) + ->log(); + return $this->fractal->item($allocation) ->transformWith($this->getTransformer(AllocationTransformer::class)) ->toArray(); @@ -106,6 +119,11 @@ public function store(NewAllocationRequest $request, Server $server): array $allocation = $this->assignableAllocationService->handle($server); + Activity::event('server:allocation.create') + ->subject($allocation) + ->property('allocation', $allocation->toString()) + ->log(); + return $this->fractal->item($allocation) ->transformWith($this->getTransformer(AllocationTransformer::class)) ->toArray(); @@ -135,6 +153,11 @@ public function delete(DeleteAllocationRequest $request, Server $server, Allocat 'server_id' => null, ]); + Activity::event('server:allocation.delete') + ->subject($allocation) + ->property('allocation', $allocation->toString()) + ->log(); + return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT); } } diff --git a/app/Http/Controllers/Api/Client/Servers/ScheduleController.php b/app/Http/Controllers/Api/Client/Servers/ScheduleController.php index 3e9b822bbd..eb92bb0028 100644 --- a/app/Http/Controllers/Api/Client/Servers/ScheduleController.php +++ b/app/Http/Controllers/Api/Client/Servers/ScheduleController.php @@ -9,6 +9,7 @@ use Pterodactyl\Models\Server; use Pterodactyl\Models\Schedule; use Illuminate\Http\JsonResponse; +use Pterodactyl\Facades\Activity; use Pterodactyl\Helpers\Utilities; use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Repositories\Eloquent\ScheduleRepository; @@ -83,6 +84,11 @@ public function store(StoreScheduleRequest $request, Server $server) 'next_run_at' => $this->getNextRunAt($request), ]); + Activity::event('server:schedule.create') + ->subject($model) + ->property('name', $model->name) + ->log(); + return $this->fractal->item($model) ->transformWith($this->getTransformer(ScheduleTransformer::class)) ->toArray(); @@ -141,6 +147,11 @@ public function update(UpdateScheduleRequest $request, Server $server, Schedule $this->repository->update($schedule->id, $data); + Activity::event('server:schedule.update') + ->subject($schedule) + ->property(['name' => $schedule->name, 'active' => $active]) + ->log(); + return $this->fractal->item($schedule->refresh()) ->transformWith($this->getTransformer(ScheduleTransformer::class)) ->toArray(); @@ -158,6 +169,8 @@ public function execute(TriggerScheduleRequest $request, Server $server, Schedul { $this->service->handle($schedule, true); + Activity::event('server:schedule.execute')->subject($schedule)->property('name', $schedule->name)->log(); + return new JsonResponse([], JsonResponse::HTTP_ACCEPTED); } @@ -170,6 +183,8 @@ public function delete(DeleteScheduleRequest $request, Server $server, Schedule { $this->repository->delete($schedule->id); + Activity::event('server:schedule.delete')->subject($schedule)->property('name', $schedule->name)->log(); + return new JsonResponse([], Response::HTTP_NO_CONTENT); } diff --git a/app/Http/Controllers/Api/Client/Servers/ScheduleTaskController.php b/app/Http/Controllers/Api/Client/Servers/ScheduleTaskController.php index 4ceed6550c..75bb48d820 100644 --- a/app/Http/Controllers/Api/Client/Servers/ScheduleTaskController.php +++ b/app/Http/Controllers/Api/Client/Servers/ScheduleTaskController.php @@ -7,6 +7,7 @@ use Pterodactyl\Models\Server; use Pterodactyl\Models\Schedule; use Illuminate\Http\JsonResponse; +use Pterodactyl\Facades\Activity; use Pterodactyl\Models\Permission; use Pterodactyl\Repositories\Eloquent\TaskRepository; use Pterodactyl\Exceptions\Http\HttpForbiddenException; @@ -67,6 +68,11 @@ public function store(StoreTaskRequest $request, Server $server, Schedule $sched 'continue_on_failure' => (bool) $request->input('continue_on_failure'), ]); + Activity::event('server:task.create') + ->subject($schedule, $task) + ->property(['name' => $schedule->name, 'action' => $task->action, 'payload' => $task->payload]) + ->log(); + return $this->fractal->item($task) ->transformWith($this->getTransformer(TaskTransformer::class)) ->toArray(); @@ -98,6 +104,11 @@ public function update(StoreTaskRequest $request, Server $server, Schedule $sche 'continue_on_failure' => (bool) $request->input('continue_on_failure'), ]); + Activity::event('server:task.update') + ->subject($schedule, $task) + ->property(['name' => $schedule->name, 'action' => $task->action, 'payload' => $task->payload]) + ->log(); + return $this->fractal->item($task->refresh()) ->transformWith($this->getTransformer(TaskTransformer::class)) ->toArray(); @@ -127,6 +138,8 @@ public function delete(ClientApiRequest $request, Server $server, Schedule $sche $task->delete(); + Activity::event('server:task.delete')->subject($schedule, $task)->property('name', $schedule->name)->log(); + return new JsonResponse(null, Response::HTTP_NO_CONTENT); } } diff --git a/app/Http/Controllers/Api/Client/Servers/SettingsController.php b/app/Http/Controllers/Api/Client/Servers/SettingsController.php index a7fbb8a75a..883362b79c 100644 --- a/app/Http/Controllers/Api/Client/Servers/SettingsController.php +++ b/app/Http/Controllers/Api/Client/Servers/SettingsController.php @@ -5,6 +5,7 @@ use Illuminate\Http\Response; use Pterodactyl\Models\Server; use Illuminate\Http\JsonResponse; +use Pterodactyl\Facades\Activity; use Pterodactyl\Repositories\Eloquent\ServerRepository; use Pterodactyl\Services\Servers\ReinstallServerService; use Pterodactyl\Http\Controllers\Api\Client\ClientApiController; @@ -52,6 +53,12 @@ public function rename(RenameServerRequest $request, Server $server) 'name' => $request->input('name'), ]); + if ($server->name !== $request->input('name')) { + Activity::event('server:settings.rename') + ->property(['old' => $server->name, 'new' => $request->input('name')]) + ->log(); + } + return new JsonResponse([], Response::HTTP_NO_CONTENT); } @@ -66,6 +73,8 @@ public function reinstall(ReinstallServerRequest $request, Server $server) { $this->reinstallServerService->handle($server); + Activity::event('server:reinstall')->log(); + return new JsonResponse([], Response::HTTP_ACCEPTED); } @@ -82,8 +91,15 @@ public function dockerImage(SetDockerImageRequest $request, Server $server) throw new BadRequestHttpException('This server\'s Docker image has been manually set by an administrator and cannot be updated.'); } + $original = $server->image; $server->forceFill(['image' => $request->input('docker_image')])->saveOrFail(); + if ($original !== $server->image) { + Activity::event('server:startup.image') + ->property(['old' => $original, 'new' => $request->input('docker_image')]) + ->log(); + } + return new JsonResponse([], Response::HTTP_NO_CONTENT); } } diff --git a/app/Http/Controllers/Api/Client/Servers/StartupController.php b/app/Http/Controllers/Api/Client/Servers/StartupController.php index 06b4a5066e..78194affdc 100644 --- a/app/Http/Controllers/Api/Client/Servers/StartupController.php +++ b/app/Http/Controllers/Api/Client/Servers/StartupController.php @@ -3,6 +3,7 @@ namespace Pterodactyl\Http\Controllers\Api\Client\Servers; use Pterodactyl\Models\Server; +use Pterodactyl\Facades\Activity; use Pterodactyl\Services\Servers\StartupCommandService; use Pterodactyl\Services\Servers\VariableValidatorService; use Pterodactyl\Repositories\Eloquent\ServerVariableRepository; @@ -75,6 +76,7 @@ public function update(UpdateStartupVariableRequest $request, Server $server) { /** @var \Pterodactyl\Models\EggVariable $variable */ $variable = $server->variables()->where('env_variable', $request->input('key'))->first(); + $original = $variable->server_value; if (is_null($variable) || !$variable->user_viewable) { throw new BadRequestHttpException('The environment variable you are trying to edit does not exist.'); @@ -97,6 +99,17 @@ public function update(UpdateStartupVariableRequest $request, Server $server) $startup = $this->startupCommandService->handle($server, false); + if ($variable->env_variable !== $request->input('value')) { + Activity::event('server:startup.edit') + ->subject($variable) + ->property([ + 'variable' => $variable->env_variable, + 'old' => $original, + 'new' => $request->input('value'), + ]) + ->log(); + } + return $this->fractal->item($variable) ->transformWith($this->getTransformer(EggVariableTransformer::class)) ->addMeta([ diff --git a/app/Http/Controllers/Api/Client/Servers/SubuserController.php b/app/Http/Controllers/Api/Client/Servers/SubuserController.php index 68f32cf8bc..ab7e5003ad 100644 --- a/app/Http/Controllers/Api/Client/Servers/SubuserController.php +++ b/app/Http/Controllers/Api/Client/Servers/SubuserController.php @@ -5,6 +5,7 @@ use Illuminate\Http\Request; use Pterodactyl\Models\Server; use Illuminate\Http\JsonResponse; +use Pterodactyl\Facades\Activity; use Pterodactyl\Models\Permission; use Illuminate\Support\Facades\Log; use Pterodactyl\Repositories\Eloquent\SubuserRepository; @@ -94,6 +95,11 @@ public function store(StoreSubuserRequest $request, Server $server) $this->getDefaultPermissions($request) ); + Activity::event('server:subuser.create') + ->subject($response->user) + ->property(['email' => $request->input('email'), 'permissions' => $this->getDefaultPermissions($request)]) + ->log(); + return $this->fractal->item($response) ->transformWith($this->getTransformer(SubuserTransformer::class)) ->toArray(); @@ -116,22 +122,37 @@ public function update(UpdateSubuserRequest $request, Server $server): array sort($permissions); sort($current); + $log = Activity::event('server:subuser.update') + ->subject($subuser->user) + ->property([ + 'email' => $subuser->user->email, + 'old' => $current, + 'new' => $permissions, + 'revoked' => true, + ]); + // Only update the database and hit up the Wings instance to invalidate JTI's if the permissions // have actually changed for the user. if ($permissions !== $current) { - $this->repository->update($subuser->id, [ - 'permissions' => $this->getDefaultPermissions($request), - ]); - - try { - $this->serverRepository->setServer($server)->revokeUserJTI($subuser->user_id); - } catch (DaemonConnectionException $exception) { - // Don't block this request if we can't connect to the Wings instance. Chances are it is - // offline in this event and the token will be invalid anyways once Wings boots back. - Log::warning($exception, ['user_id' => $subuser->user_id, 'server_id' => $server->id]); - } + $log->transaction(function ($instance) use ($request, $subuser, $server) { + $this->repository->update($subuser->id, [ + 'permissions' => $this->getDefaultPermissions($request), + ]); + + try { + $this->serverRepository->setServer($server)->revokeUserJTI($subuser->user_id); + } catch (DaemonConnectionException $exception) { + // Don't block this request if we can't connect to the Wings instance. Chances are it is + // offline in this event and the token will be invalid anyways once Wings boots back. + Log::warning($exception, ['user_id' => $subuser->user_id, 'server_id' => $server->id]); + + $instance->property('revoked', false); + } + }); } + $log->reset(); + return $this->fractal->item($subuser->refresh()) ->transformWith($this->getTransformer(SubuserTransformer::class)) ->toArray(); @@ -147,14 +168,23 @@ public function delete(DeleteSubuserRequest $request, Server $server) /** @var \Pterodactyl\Models\Subuser $subuser */ $subuser = $request->attributes->get('subuser'); - $this->repository->delete($subuser->id); + $log = Activity::event('server:subuser.delete') + ->subject($subuser->user) + ->property('email', $subuser->user->email) + ->property('revoked', true); - try { - $this->serverRepository->setServer($server)->revokeUserJTI($subuser->user_id); - } catch (DaemonConnectionException $exception) { - // Don't block this request if we can't connect to the Wings instance. - Log::warning($exception, ['user_id' => $subuser->user_id, 'server_id' => $server->id]); - } + $log->transaction(function ($instance) use ($server, $subuser) { + $subuser->delete(); + + try { + $this->serverRepository->setServer($server)->revokeUserJTI($subuser->user_id); + } catch (DaemonConnectionException $exception) { + // Don't block this request if we can't connect to the Wings instance. + Log::warning($exception, ['user_id' => $subuser->user_id, 'server_id' => $server->id]); + + $instance->property('revoked', false); + } + }); return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT); } diff --git a/app/Models/Allocation.php b/app/Models/Allocation.php index 47b560e487..44b4f5bf12 100644 --- a/app/Models/Allocation.php +++ b/app/Models/Allocation.php @@ -122,6 +122,11 @@ public function getHasAliasAttribute($value) return !is_null($this->ip_alias); } + public function toString(): string + { + return sprintf('%s:%s', $this->ip, $this->port); + } + /** * Gets information for the server associated with this allocation. * diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 5b335f9a50..5b38a41757 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -4,12 +4,8 @@ use View; use Cache; +use Pterodactyl\Models; use Illuminate\Support\Str; -use Pterodactyl\Models\User; -use Pterodactyl\Models\Server; -use Pterodactyl\Models\Backup; -use Pterodactyl\Models\ApiKey; -use Pterodactyl\Models\UserSSHKey; use Illuminate\Support\Facades\URL; use Illuminate\Pagination\Paginator; use Illuminate\Support\Facades\Schema; @@ -41,11 +37,17 @@ public function boot() } Relation::enforceMorphMap([ - 'api_key' => ApiKey::class, - 'backup' => Backup::class, - 'server' => Server::class, - 'ssh_key' => UserSSHKey::class, - 'user' => User::class, + 'allocation' => Models\Allocation::class, + 'api_key' => Models\ApiKey::class, + 'backup' => Models\Backup::class, + 'database' => Models\Database::class, + 'egg' => Models\Egg::class, + 'egg_variable' => Models\EggVariable::class, + 'schedule' => Models\Schedule::class, + 'server' => Models\Server::class, + 'ssh_key' => Models\UserSSHKey::class, + 'task' => Models\Task::class, + 'user' => Models\User::class, ]); } diff --git a/app/Services/Activity/ActivityLogService.php b/app/Services/Activity/ActivityLogService.php index 9726b657fd..cb45dc33de 100644 --- a/app/Services/Activity/ActivityLogService.php +++ b/app/Services/Activity/ActivityLogService.php @@ -188,6 +188,15 @@ public function transaction(\Closure $callback) }); } + /** + * Resets the instance and clears out the log. + */ + public function reset(): void + { + $this->activity = null; + $this->subjects = []; + } + /** * Returns the current activity log instance. */ From e15985ea39de5de1b23dc4f950c62fac83c4ed79 Mon Sep 17 00:00:00 2001 From: DaneEveritt Date: Sun, 29 May 2022 19:45:00 -0400 Subject: [PATCH 069/458] Add support for automatically pruning activity logs --- app/Console/Kernel.php | 15 +++++++++++---- app/Models/ActivityLog.php | 19 +++++++++++++++++++ config/activity.php | 9 +++++++++ ...40349_create_activity_log_actors_table.php | 4 ++-- 4 files changed, 41 insertions(+), 6 deletions(-) create mode 100644 config/activity.php diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index d5fa25ec4c..17b3d4de44 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -2,8 +2,13 @@ namespace Pterodactyl\Console; +use Pterodactyl\Models\ActivityLog; use Illuminate\Console\Scheduling\Schedule; +use Illuminate\Database\Console\PruneCommand; use Illuminate\Foundation\Console\Kernel as ConsoleKernel; +use Pterodactyl\Console\Commands\Schedule\ProcessRunnableCommand; +use Pterodactyl\Console\Commands\Maintenance\PruneOrphanedBackupsCommand; +use Pterodactyl\Console\Commands\Maintenance\CleanServiceBackupFilesCommand; class Kernel extends ConsoleKernel { @@ -21,14 +26,16 @@ protected function commands() protected function schedule(Schedule $schedule) { // Execute scheduled commands for servers every minute, as if there was a normal cron running. - $schedule->command('p:schedule:process')->everyMinute()->withoutOverlapping(); + $schedule->command(ProcessRunnableCommand::class)->everyMinute()->withoutOverlapping(); + $schedule->command(CleanServiceBackupFilesCommand::class)->daily(); if (config('backups.prune_age')) { // Every 30 minutes, run the backup pruning command so that any abandoned backups can be deleted. - $schedule->command('p:maintenance:prune-backups')->everyThirtyMinutes(); + $schedule->command(PruneOrphanedBackupsCommand::class)->everyThirtyMinutes(); } - // Every day cleanup any internal backups of service files. - $schedule->command('p:maintenance:clean-service-backups')->daily(); + if (config('activity.prune_days')) { + $schedule->command(PruneCommand::class, ['--model' => [ActivityLog::class]])->daily(); + } } } diff --git a/app/Models/ActivityLog.php b/app/Models/ActivityLog.php index 0681b21ca0..4312e332e9 100644 --- a/app/Models/ActivityLog.php +++ b/app/Models/ActivityLog.php @@ -2,9 +2,12 @@ namespace Pterodactyl\Models; +use Carbon\Carbon; +use LogicException; use Illuminate\Support\Facades\Event; use Pterodactyl\Events\ActivityLogged; use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Eloquent\MassPrunable; use Illuminate\Database\Eloquent\Relations\MorphTo; use Illuminate\Database\Eloquent\Model as IlluminateModel; @@ -42,6 +45,8 @@ */ class ActivityLog extends Model { + use MassPrunable; + public $timestamps = false; protected $guarded = [ @@ -86,6 +91,20 @@ public function scopeForActor(Builder $builder, IlluminateModel $actor): Builder return $builder->whereMorphedTo('actor', $actor); } + /** + * Returns models to be pruned. + * + * @see https://laravel.com/docs/9.x/eloquent#pruning-models + */ + public function prunable() + { + if (is_null(config('activity.prune_days'))) { + throw new LogicException('Cannot prune activity logs: no "prune_days" configuration value is set.'); + } + + return static::where('timestamp', '<=', Carbon::now()->subDays(config('activity.prune_days'))); + } + /** * Boots the model event listeners. This will trigger an activity log event every * time a new model is inserted which can then be captured and worked with as needed. diff --git a/config/activity.php b/config/activity.php new file mode 100644 index 0000000000..642be06306 --- /dev/null +++ b/config/activity.php @@ -0,0 +1,9 @@ + env('APP_ACTIVITY_PRUNE_DAYS', 90), +]; diff --git a/database/migrations/2022_05_29_140349_create_activity_log_actors_table.php b/database/migrations/2022_05_29_140349_create_activity_log_actors_table.php index 8be57bc1c9..6dc45d7f8a 100644 --- a/database/migrations/2022_05_29_140349_create_activity_log_actors_table.php +++ b/database/migrations/2022_05_29_140349_create_activity_log_actors_table.php @@ -15,7 +15,7 @@ public function up() { Schema::create('activity_log_subjects', function (Blueprint $table) { $table->id(); - $table->foreignId('activity_log_id')->references('id')->on('activity_logs'); + $table->foreignId('activity_log_id')->references('id')->on('activity_logs')->cascadeOnDelete(); $table->numericMorphs('subject'); }); } @@ -27,6 +27,6 @@ public function up() */ public function down() { - Schema::dropIfExists('activity_log_subject'); + Schema::dropIfExists('activity_log_subjects'); } } From a5521ecb7944caa3a468f5bd8f185612892e1aa5 Mon Sep 17 00:00:00 2001 From: DaneEveritt Date: Sun, 29 May 2022 20:34:48 -0400 Subject: [PATCH 070/458] Add support for returning transforming activity logs on the front-end --- .../Api/Client/ActivityLogController.php | 30 +++++++++++++++ app/Models/ActivityLog.php | 5 ++- .../Api/Client/ActivityLogTransformer.php | 37 +++++++++++++++++++ resources/scripts/api/definitions/helpers.ts | 26 +++++++++++++ resources/scripts/api/definitions/index.d.ts | 3 +- .../scripts/api/definitions/user/models.d.ts | 26 ++++++++++++- .../api/definitions/user/transformers.ts | 37 ++++++++++++++++++- resources/scripts/api/http.ts | 2 +- routes/api-client.php | 2 + 9 files changed, 162 insertions(+), 6 deletions(-) create mode 100644 app/Http/Controllers/Api/Client/ActivityLogController.php create mode 100644 app/Transformers/Api/Client/ActivityLogTransformer.php create mode 100644 resources/scripts/api/definitions/helpers.ts diff --git a/app/Http/Controllers/Api/Client/ActivityLogController.php b/app/Http/Controllers/Api/Client/ActivityLogController.php new file mode 100644 index 0000000000..70e1b23936 --- /dev/null +++ b/app/Http/Controllers/Api/Client/ActivityLogController.php @@ -0,0 +1,30 @@ +user()->activity()) + ->with('actor') + ->allowedFilters([ + AllowedFilter::exact('ip'), + AllowedFilter::partial('event'), + ]) + ->paginate(min($request->query('per_page', 50), 100)) + ->appends($request->query()); + + return $this->fractal->collection($activity) + ->transformWith($this->getTransformer(ActivityLogTransformer::class)) + ->toArray(); + } +} diff --git a/app/Models/ActivityLog.php b/app/Models/ActivityLog.php index 4312e332e9..b914d9513b 100644 --- a/app/Models/ActivityLog.php +++ b/app/Models/ActivityLog.php @@ -22,7 +22,7 @@ * @property string|null $actor_type * @property int|null $actor_id * @property \Illuminate\Support\Collection|null $properties - * @property string $timestamp + * @property \Carbon\Carbon $timestamp * @property IlluminateModel|\Eloquent $actor * @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\ActivityLogSubject[] $subjects * @property int|null $subjects_count @@ -47,6 +47,8 @@ class ActivityLog extends Model { use MassPrunable; + public const RESOURCE_NAME = 'activity_log'; + public $timestamps = false; protected $guarded = [ @@ -56,6 +58,7 @@ class ActivityLog extends Model protected $casts = [ 'properties' => 'collection', + 'timestamp' => 'datetime', ]; protected $with = ['subjects']; diff --git a/app/Transformers/Api/Client/ActivityLogTransformer.php b/app/Transformers/Api/Client/ActivityLogTransformer.php new file mode 100644 index 0000000000..077b11b7f5 --- /dev/null +++ b/app/Transformers/Api/Client/ActivityLogTransformer.php @@ -0,0 +1,37 @@ + $model->batch, + 'event' => $model->event, + 'ip' => $model->ip, + 'description' => $model->description, + 'properties' => $model->properties, + 'timestamp' => $model->timestamp->toIso8601String(), + ]; + } + + public function includeActor(ActivityLog $model) + { + if (!$model->actor instanceof User) { + return $this->null(); + } + + return $this->item($model->actor, $this->makeTransformer(UserTransformer::class), User::RESOURCE_NAME); + } +} diff --git a/resources/scripts/api/definitions/helpers.ts b/resources/scripts/api/definitions/helpers.ts new file mode 100644 index 0000000000..5e1afd656c --- /dev/null +++ b/resources/scripts/api/definitions/helpers.ts @@ -0,0 +1,26 @@ +import { FractalResponseData, FractalResponseList } from '@/api/http'; + +type Transformer = (callback: FractalResponseData) => T; + +const isList = (data: FractalResponseList | FractalResponseData): data is FractalResponseList => data.object === 'list'; + +function transform(data: null | undefined, transformer: Transformer, missing?: M): M; +function transform(data: FractalResponseData | null | undefined, transformer: Transformer, missing?: M): T | M; +function transform(data: FractalResponseList | null | undefined, transformer: Transformer, missing?: M): T[] | M; +function transform (data: FractalResponseData | FractalResponseList | null | undefined, transformer: Transformer, missing = undefined) { + if (data === undefined || data === null) { + return missing; + } + + if (isList(data)) { + return data.data.map(transformer); + } + + if (!data || !data.attributes || data.object === 'null_resource') { + return missing; + } + + return transformer(data); +} + +export { transform }; diff --git a/resources/scripts/api/definitions/index.d.ts b/resources/scripts/api/definitions/index.d.ts index 68925c8633..1612873840 100644 --- a/resources/scripts/api/definitions/index.d.ts +++ b/resources/scripts/api/definitions/index.d.ts @@ -1,4 +1,5 @@ import { MarkRequired } from 'ts-essentials'; +import { FractalResponseData, FractalResponseList } from '../http'; export type UUID = string; @@ -6,7 +7,7 @@ export type UUID = string; export interface Model {} interface ModelWithRelationships extends Model { - relationships: Record; + relationships: Record; } /** diff --git a/resources/scripts/api/definitions/user/models.d.ts b/resources/scripts/api/definitions/user/models.d.ts index 51bea475c7..1f26683aa6 100644 --- a/resources/scripts/api/definitions/user/models.d.ts +++ b/resources/scripts/api/definitions/user/models.d.ts @@ -1,4 +1,16 @@ -import { Model } from '@/api/definitions'; +import { Model, UUID } from '@/api/definitions'; +import { SubuserPermission } from '@/state/server/subusers'; + +interface User extends Model { + uuid: string; + username: string; + email: string; + image: string; + twoFactorEnabled: boolean; + createdAt: Date; + permissions: SubuserPermission[]; + can (permission: SubuserPermission): boolean; +} interface SSHKey extends Model { name: string; @@ -6,3 +18,15 @@ interface SSHKey extends Model { fingerprint: string; createdAt: Date; } + +interface ActivityLog extends Model<'actor'> { + batch: UUID | null; + event: string; + ip: string; + description: string | null; + properties: Record; + timestamp: Date; + relationships: { + actor: User | null; + } +} diff --git a/resources/scripts/api/definitions/user/transformers.ts b/resources/scripts/api/definitions/user/transformers.ts index 89adbad754..6532cdd938 100644 --- a/resources/scripts/api/definitions/user/transformers.ts +++ b/resources/scripts/api/definitions/user/transformers.ts @@ -1,7 +1,9 @@ -import { SSHKey } from '@definitions/user/models'; +import * as Models from '@definitions/user/models'; +import { FractalResponseData } from '@/api/http'; +import { transform } from '@definitions/helpers'; export default class Transformers { - static toSSHKey (data: Record): SSHKey { + static toSSHKey (data: Record): Models.SSHKey { return { name: data.name, publicKey: data.public_key, @@ -9,6 +11,37 @@ export default class Transformers { createdAt: new Date(data.created_at), }; } + + static toUser ({ attributes }: FractalResponseData): Models.User { + return { + uuid: attributes.uuid, + username: attributes.username, + email: attributes.email, + image: attributes.image, + twoFactorEnabled: attributes['2fa_enabled'], + permissions: attributes.permissions || [], + createdAt: new Date(attributes.created_at), + can (permission): boolean { + return this.permissions.includes(permission); + }, + }; + } + + static toActivityLog ({ attributes }: FractalResponseData): Models.ActivityLog { + const { actor } = attributes.relationships || {}; + + return { + batch: attributes.batch, + event: attributes.event, + ip: attributes.ip, + description: attributes.description, + properties: attributes.properties, + timestamp: new Date(attributes.timestamp), + relationships: { + actor: transform(actor as FractalResponseData, this.toUser, null), + }, + }; + } } export class MetaTransformers { diff --git a/resources/scripts/api/http.ts b/resources/scripts/api/http.ts index 78aa393ee0..cd6b8e512a 100644 --- a/resources/scripts/api/http.ts +++ b/resources/scripts/api/http.ts @@ -68,7 +68,7 @@ export interface FractalResponseData { object: string; attributes: { [k: string]: any; - relationships?: Record; + relationships?: Record; }; } diff --git a/routes/api-client.php b/routes/api-client.php index 3099ea0d8c..dc88eaee0b 100644 --- a/routes/api-client.php +++ b/routes/api-client.php @@ -30,6 +30,8 @@ Route::put('/email', [Client\AccountController::class, 'updateEmail'])->name('api:client.account.update-email'); Route::put('/password', [Client\AccountController::class, 'updatePassword'])->name('api:client.account.update-password'); + Route::get('/activity', Client\ActivityLogController::class)->name('api:client.account.activity'); + Route::get('/api-keys', [Client\ApiKeyController::class, 'index']); Route::post('/api-keys', [Client\ApiKeyController::class, 'store']); Route::delete('/api-keys/{identifier}', [Client\ApiKeyController::class, 'delete']); From 9300e1116df67f11ad6b03b30ddeaedc9ffe7f8f Mon Sep 17 00:00:00 2001 From: DaneEveritt Date: Sun, 29 May 2022 20:39:51 -0400 Subject: [PATCH 071/458] Fix failing tests --- app/Http/Controllers/Api/Client/ApiKeyController.php | 5 +++-- tests/Integration/Api/Client/SSHKeyControllerTest.php | 4 ++-- tests/Integration/IntegrationTestCase.php | 4 ++++ 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/app/Http/Controllers/Api/Client/ApiKeyController.php b/app/Http/Controllers/Api/Client/ApiKeyController.php index 5e888e91df..5f0af6c8bf 100644 --- a/app/Http/Controllers/Api/Client/ApiKeyController.php +++ b/app/Http/Controllers/Api/Client/ApiKeyController.php @@ -60,13 +60,14 @@ public function store(StoreApiKeyRequest $request) */ public function delete(ClientApiRequest $request, string $identifier) { + /** @var \Pterodactyl\Models\ApiKey $key */ $key = $request->user()->apiKeys() ->where('key_type', ApiKey::TYPE_ACCOUNT) ->where('identifier', $identifier) - ->first(); + ->firstOrFail(); Activity::event('user:api-key.delete') - ->property('identifer', $key->identifer) + ->property('identifer', $key->identifier) ->log(); $key->delete(); diff --git a/tests/Integration/Api/Client/SSHKeyControllerTest.php b/tests/Integration/Api/Client/SSHKeyControllerTest.php index 6744a12951..2af492f569 100644 --- a/tests/Integration/Api/Client/SSHKeyControllerTest.php +++ b/tests/Integration/Api/Client/SSHKeyControllerTest.php @@ -46,8 +46,8 @@ public function testSSHKeyCanBeDeleted() $this->assertSoftDeleted($key); $this->assertNotSoftDeleted($key2); - $this->deleteJson($this->link($key))->assertNoContent(); - $this->deleteJson($this->link($key2))->assertNoContent(); + $this->deleteJson($this->link($key))->assertNotFound(); + $this->deleteJson($this->link($key2))->assertNotFound(); $this->assertNotSoftDeleted($key2); } diff --git a/tests/Integration/IntegrationTestCase.php b/tests/Integration/IntegrationTestCase.php index c290f6070b..252d14d8cc 100644 --- a/tests/Integration/IntegrationTestCase.php +++ b/tests/Integration/IntegrationTestCase.php @@ -13,6 +13,10 @@ abstract class IntegrationTestCase extends TestCase protected array $connectionsToTransact = ['mysql']; + protected $defaultHeaders = [ + 'Accept' => 'application/json', + ]; + /** * Return an ISO-8601 formatted timestamp to use in the API response. */ From e5fec9934d9ee67a1afedbb4d28cd64ac755c08c Mon Sep 17 00:00:00 2001 From: DaneEveritt Date: Sun, 29 May 2022 20:42:55 -0400 Subject: [PATCH 072/458] Update CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index eb0e8d00e4..2171fedb4e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,10 +40,12 @@ be considered deprecated, but will continue to work. Application keys _will not_ * Adds command to return a list of all nodes available on the Panel in both table and JSON format (`php artisan p:node:list`). * Adds server network (inbound/outbound) usage graphs to the console screen. * Adds support for configuring CORS on the API by setting the `APP_CORS_ALLOWED_ORIGINS=example.com,dashboard.example.com` environment variable. By default all instances are configured with this set to `*` which allows any origin. +* Adds proper activity logging for the following areas of the Panel: authentication, user account modifications, server modification. This is an initial test implementation before further roll-out in the software. Events are logged into the database but are not currently exposed in the UI — they will be displayed in a future update. ### Removed * Removes Google Analytics from the front end code. * Removes multiple middleware that were previously used for configuring API access and controlling model fetching. This has all been replaced with Laravel Sanctum and standard Laravel API tooling. This should make codebase discovery significantly more simple. +* **DEPRECATED**: The use of `Pterodactyl\Models\AuditLog` is deprecated and all references to this model have been removed from the codebase. In the next major release this model and table will be fully dropped. ## v1.7.0 ### Fixed From 8cf1311b8454283a33e29f67b5174163094e34c4 Mon Sep 17 00:00:00 2001 From: DaneEveritt Date: Sun, 29 May 2022 20:43:52 -0400 Subject: [PATCH 073/458] Update dependencies prior to release tag --- composer.lock | 255 +++++++++++++++++++++++++------------------------- 1 file changed, 126 insertions(+), 129 deletions(-) diff --git a/composer.lock b/composer.lock index b13af61adb..0f8e5155e4 100644 --- a/composer.lock +++ b/composer.lock @@ -58,16 +58,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.222.5", + "version": "3.224.0", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "26596820b043db985bf7c9deb98aa2234f6054cb" + "reference": "bc5eb18414ef703c5f39a5a009a437c74c228306" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/26596820b043db985bf7c9deb98aa2234f6054cb", - "reference": "26596820b043db985bf7c9deb98aa2234f6054cb", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/bc5eb18414ef703c5f39a5a009a437c74c228306", + "reference": "bc5eb18414ef703c5f39a5a009a437c74c228306", "shasum": "" }, "require": { @@ -143,9 +143,9 @@ "support": { "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.222.5" + "source": "https://github.com/aws/aws-sdk-php/tree/3.224.0" }, - "time": "2022-05-04T20:25:34+00:00" + "time": "2022-05-27T20:23:28+00:00" }, { "name": "brick/math", @@ -284,16 +284,16 @@ }, { "name": "doctrine/cache", - "version": "2.1.1", + "version": "2.2.0", "source": { "type": "git", "url": "https://github.com/doctrine/cache.git", - "reference": "331b4d5dbaeab3827976273e9356b3b453c300ce" + "reference": "1ca8f21980e770095a31456042471a57bc4c68fb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/cache/zipball/331b4d5dbaeab3827976273e9356b3b453c300ce", - "reference": "331b4d5dbaeab3827976273e9356b3b453c300ce", + "url": "https://api.github.com/repos/doctrine/cache/zipball/1ca8f21980e770095a31456042471a57bc4c68fb", + "reference": "1ca8f21980e770095a31456042471a57bc4c68fb", "shasum": "" }, "require": { @@ -303,18 +303,12 @@ "doctrine/common": ">2.2,<2.4" }, "require-dev": { - "alcaeus/mongo-php-adapter": "^1.1", "cache/integration-tests": "dev-master", - "doctrine/coding-standard": "^8.0", - "mongodb/mongodb": "^1.1", - "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", - "predis/predis": "~1.0", + "doctrine/coding-standard": "^9", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", "psr/cache": "^1.0 || ^2.0 || ^3.0", - "symfony/cache": "^4.4 || ^5.2 || ^6.0@dev", - "symfony/var-exporter": "^4.4 || ^5.2 || ^6.0@dev" - }, - "suggest": { - "alcaeus/mongo-php-adapter": "Required to use legacy MongoDB driver" + "symfony/cache": "^4.4 || ^5.4 || ^6", + "symfony/var-exporter": "^4.4 || ^5.4 || ^6" }, "type": "library", "autoload": { @@ -363,7 +357,7 @@ ], "support": { "issues": "https://github.com/doctrine/cache/issues", - "source": "https://github.com/doctrine/cache/tree/2.1.1" + "source": "https://github.com/doctrine/cache/tree/2.2.0" }, "funding": [ { @@ -379,7 +373,7 @@ "type": "tidelift" } ], - "time": "2021-07-17T14:49:29+00:00" + "time": "2022-05-20T20:07:39+00:00" }, { "name": "doctrine/dbal", @@ -1137,16 +1131,16 @@ }, { "name": "guzzlehttp/guzzle", - "version": "7.4.2", + "version": "7.4.3", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "ac1ec1cd9b5624694c3a40be801d94137afb12b4" + "reference": "74a8602c6faec9ef74b7a9391ac82c5e65b1cdab" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/ac1ec1cd9b5624694c3a40be801d94137afb12b4", - "reference": "ac1ec1cd9b5624694c3a40be801d94137afb12b4", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/74a8602c6faec9ef74b7a9391ac82c5e65b1cdab", + "reference": "74a8602c6faec9ef74b7a9391ac82c5e65b1cdab", "shasum": "" }, "require": { @@ -1241,7 +1235,7 @@ ], "support": { "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.4.2" + "source": "https://github.com/guzzle/guzzle/tree/7.4.3" }, "funding": [ { @@ -1257,7 +1251,7 @@ "type": "tidelift" } ], - "time": "2022-03-20T14:16:28+00:00" + "time": "2022-05-25T13:24:33+00:00" }, { "name": "guzzlehttp/promises", @@ -1591,16 +1585,16 @@ }, { "name": "laravel/framework", - "version": "v8.83.11", + "version": "v8.83.14", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "d85c34179f209977043502441f9e44ca432a14b4" + "reference": "141cf126f1746c7264f59aa78c923a84eaab501e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/d85c34179f209977043502441f9e44ca432a14b4", - "reference": "d85c34179f209977043502441f9e44ca432a14b4", + "url": "https://api.github.com/repos/laravel/framework/zipball/141cf126f1746c7264f59aa78c923a84eaab501e", + "reference": "141cf126f1746c7264f59aa78c923a84eaab501e", "shasum": "" }, "require": { @@ -1760,7 +1754,7 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2022-05-03T14:47:00+00:00" + "time": "2022-05-24T14:04:02+00:00" }, { "name": "laravel/helpers", @@ -1885,16 +1879,16 @@ }, { "name": "laravel/serializable-closure", - "version": "v1.1.1", + "version": "v1.2.0", "source": { "type": "git", "url": "https://github.com/laravel/serializable-closure.git", - "reference": "9e4b005daa20b0c161f3845040046dc9ddc1d74e" + "reference": "09f0e9fb61829f628205b7c94906c28740ff9540" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/9e4b005daa20b0c161f3845040046dc9ddc1d74e", - "reference": "9e4b005daa20b0c161f3845040046dc9ddc1d74e", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/09f0e9fb61829f628205b7c94906c28740ff9540", + "reference": "09f0e9fb61829f628205b7c94906c28740ff9540", "shasum": "" }, "require": { @@ -1940,7 +1934,7 @@ "issues": "https://github.com/laravel/serializable-closure/issues", "source": "https://github.com/laravel/serializable-closure" }, - "time": "2022-02-11T19:23:53+00:00" + "time": "2022-05-16T17:09:47+00:00" }, { "name": "laravel/tinker", @@ -2012,16 +2006,16 @@ }, { "name": "laravel/ui", - "version": "v3.4.5", + "version": "v3.4.6", "source": { "type": "git", "url": "https://github.com/laravel/ui.git", - "reference": "f11d295de1508c5bb56206a620b00b6616de414c" + "reference": "65ec5c03f7fee2c8ecae785795b829a15be48c2c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/ui/zipball/f11d295de1508c5bb56206a620b00b6616de414c", - "reference": "f11d295de1508c5bb56206a620b00b6616de414c", + "url": "https://api.github.com/repos/laravel/ui/zipball/65ec5c03f7fee2c8ecae785795b829a15be48c2c", + "reference": "65ec5c03f7fee2c8ecae785795b829a15be48c2c", "shasum": "" }, "require": { @@ -2067,9 +2061,9 @@ "ui" ], "support": { - "source": "https://github.com/laravel/ui/tree/v3.4.5" + "source": "https://github.com/laravel/ui/tree/v3.4.6" }, - "time": "2022-02-21T14:59:16+00:00" + "time": "2022-05-20T13:38:08+00:00" }, { "name": "lcobucci/clock", @@ -2208,16 +2202,16 @@ }, { "name": "league/commonmark", - "version": "2.3.0", + "version": "2.3.1", "source": { "type": "git", "url": "https://github.com/thephpleague/commonmark.git", - "reference": "32a49eb2b38fe5e5c417ab748a45d0beaab97955" + "reference": "cb36fee279f7fca01d5d9399ddd1b37e48e2eca1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/32a49eb2b38fe5e5c417ab748a45d0beaab97955", - "reference": "32a49eb2b38fe5e5c417ab748a45d0beaab97955", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/cb36fee279f7fca01d5d9399ddd1b37e48e2eca1", + "reference": "cb36fee279f7fca01d5d9399ddd1b37e48e2eca1", "shasum": "" }, "require": { @@ -2310,7 +2304,7 @@ "type": "tidelift" } ], - "time": "2022-04-07T22:37:05+00:00" + "time": "2022-05-14T15:37:39+00:00" }, { "name": "league/config", @@ -2770,16 +2764,16 @@ }, { "name": "monolog/monolog", - "version": "2.5.0", + "version": "2.6.0", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "4192345e260f1d51b365536199744b987e160edc" + "reference": "247918972acd74356b0a91dfaa5adcaec069b6c0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/4192345e260f1d51b365536199744b987e160edc", - "reference": "4192345e260f1d51b365536199744b987e160edc", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/247918972acd74356b0a91dfaa5adcaec069b6c0", + "reference": "247918972acd74356b0a91dfaa5adcaec069b6c0", "shasum": "" }, "require": { @@ -2792,18 +2786,23 @@ "require-dev": { "aws/aws-sdk-php": "^2.4.9 || ^3.0", "doctrine/couchdb": "~1.0@dev", - "elasticsearch/elasticsearch": "^7", + "elasticsearch/elasticsearch": "^7 || ^8", + "ext-json": "*", "graylog2/gelf-php": "^1.4.2", + "guzzlehttp/guzzle": "^7.4", + "guzzlehttp/psr7": "^2.2", "mongodb/mongodb": "^1.8", "php-amqplib/php-amqplib": "~2.4 || ^3", "php-console/php-console": "^3.1.3", - "phpspec/prophecy": "^1.6.1", + "phpspec/prophecy": "^1.15", "phpstan/phpstan": "^0.12.91", - "phpunit/phpunit": "^8.5", + "phpunit/phpunit": "^8.5.14", "predis/predis": "^1.1", "rollbar/rollbar": "^1.3 || ^2 || ^3", - "ruflin/elastica": ">=0.90@dev", - "swiftmailer/swiftmailer": "^5.3|^6.0" + "ruflin/elastica": "^7", + "swiftmailer/swiftmailer": "^5.3|^6.0", + "symfony/mailer": "^5.4 || ^6", + "symfony/mime": "^5.4 || ^6" }, "suggest": { "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", @@ -2853,7 +2852,7 @@ ], "support": { "issues": "https://github.com/Seldaek/monolog/issues", - "source": "https://github.com/Seldaek/monolog/tree/2.5.0" + "source": "https://github.com/Seldaek/monolog/tree/2.6.0" }, "funding": [ { @@ -2865,7 +2864,7 @@ "type": "tidelift" } ], - "time": "2022-04-08T15:43:54+00:00" + "time": "2022-05-10T09:36:00+00:00" }, { "name": "mtdowling/jmespath.php", @@ -4196,16 +4195,16 @@ }, { "name": "psy/psysh", - "version": "v0.11.2", + "version": "v0.11.5", "source": { "type": "git", "url": "https://github.com/bobthecow/psysh.git", - "reference": "7f7da640d68b9c9fec819caae7c744a213df6514" + "reference": "c23686f9c48ca202710dbb967df8385a952a2daf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bobthecow/psysh/zipball/7f7da640d68b9c9fec819caae7c744a213df6514", - "reference": "7f7da640d68b9c9fec819caae7c744a213df6514", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/c23686f9c48ca202710dbb967df8385a952a2daf", + "reference": "c23686f9c48ca202710dbb967df8385a952a2daf", "shasum": "" }, "require": { @@ -4220,15 +4219,13 @@ "symfony/console": "4.4.37 || 5.3.14 || 5.3.15 || 5.4.3 || 5.4.4 || 6.0.3 || 6.0.4" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.2", - "hoa/console": "3.17.05.02" + "bamarni/composer-bin-plugin": "^1.2" }, "suggest": { "ext-pcntl": "Enabling the PCNTL extension makes PsySH a lot happier :)", "ext-pdo-sqlite": "The doc command requires SQLite to work.", "ext-posix": "If you have PCNTL, you'll want the POSIX extension as well.", - "ext-readline": "Enables support for arrow-key history navigation, and showing and manipulating command history.", - "hoa/console": "A pure PHP readline implementation. You'll want this if your PHP install doesn't already support readline or libedit." + "ext-readline": "Enables support for arrow-key history navigation, and showing and manipulating command history." }, "bin": [ "bin/psysh" @@ -4268,9 +4265,9 @@ ], "support": { "issues": "https://github.com/bobthecow/psysh/issues", - "source": "https://github.com/bobthecow/psysh/tree/v0.11.2" + "source": "https://github.com/bobthecow/psysh/tree/v0.11.5" }, - "time": "2022-02-28T15:28:54+00:00" + "time": "2022-05-27T18:03:49+00:00" }, { "name": "ralouphie/getallheaders", @@ -4890,16 +4887,16 @@ }, { "name": "symfony/console", - "version": "v5.4.8", + "version": "v5.4.9", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "ffe3aed36c4d60da2cf1b0a1cee6b8f2e5fa881b" + "reference": "829d5d1bf60b2efeb0887b7436873becc71a45eb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/ffe3aed36c4d60da2cf1b0a1cee6b8f2e5fa881b", - "reference": "ffe3aed36c4d60da2cf1b0a1cee6b8f2e5fa881b", + "url": "https://api.github.com/repos/symfony/console/zipball/829d5d1bf60b2efeb0887b7436873becc71a45eb", + "reference": "829d5d1bf60b2efeb0887b7436873becc71a45eb", "shasum": "" }, "require": { @@ -4969,7 +4966,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.4.8" + "source": "https://github.com/symfony/console/tree/v5.4.9" }, "funding": [ { @@ -4985,7 +4982,7 @@ "type": "tidelift" } ], - "time": "2022-04-12T16:02:29+00:00" + "time": "2022-05-18T06:17:34+00:00" }, { "name": "symfony/css-selector", @@ -5122,16 +5119,16 @@ }, { "name": "symfony/error-handler", - "version": "v5.4.8", + "version": "v5.4.9", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "c1fcde614dfe99d62a83b796a53b8bad358b266a" + "reference": "c116cda1f51c678782768dce89a45f13c949455d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/c1fcde614dfe99d62a83b796a53b8bad358b266a", - "reference": "c1fcde614dfe99d62a83b796a53b8bad358b266a", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/c116cda1f51c678782768dce89a45f13c949455d", + "reference": "c116cda1f51c678782768dce89a45f13c949455d", "shasum": "" }, "require": { @@ -5173,7 +5170,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v5.4.8" + "source": "https://github.com/symfony/error-handler/tree/v5.4.9" }, "funding": [ { @@ -5189,20 +5186,20 @@ "type": "tidelift" } ], - "time": "2022-04-12T15:48:08+00:00" + "time": "2022-05-21T13:57:48+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v5.4.3", + "version": "v5.4.9", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "dec8a9f58d20df252b9cd89f1c6c1530f747685d" + "reference": "8e6ce1cc0279e3ff3c8ff0f43813bc88d21ca1bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/dec8a9f58d20df252b9cd89f1c6c1530f747685d", - "reference": "dec8a9f58d20df252b9cd89f1c6c1530f747685d", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/8e6ce1cc0279e3ff3c8ff0f43813bc88d21ca1bc", + "reference": "8e6ce1cc0279e3ff3c8ff0f43813bc88d21ca1bc", "shasum": "" }, "require": { @@ -5258,7 +5255,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v5.4.3" + "source": "https://github.com/symfony/event-dispatcher/tree/v5.4.9" }, "funding": [ { @@ -5274,7 +5271,7 @@ "type": "tidelift" } ], - "time": "2022-01-02T09:53:40+00:00" + "time": "2022-05-05T16:45:39+00:00" }, { "name": "symfony/event-dispatcher-contracts", @@ -5420,16 +5417,16 @@ }, { "name": "symfony/http-foundation", - "version": "v5.4.8", + "version": "v5.4.9", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "ff2818d1c3d49860bcae1f2cbb5eb00fcd3bf9e2" + "reference": "6b0d0e4aca38d57605dcd11e2416994b38774522" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/ff2818d1c3d49860bcae1f2cbb5eb00fcd3bf9e2", - "reference": "ff2818d1c3d49860bcae1f2cbb5eb00fcd3bf9e2", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/6b0d0e4aca38d57605dcd11e2416994b38774522", + "reference": "6b0d0e4aca38d57605dcd11e2416994b38774522", "shasum": "" }, "require": { @@ -5473,7 +5470,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v5.4.8" + "source": "https://github.com/symfony/http-foundation/tree/v5.4.9" }, "funding": [ { @@ -5489,20 +5486,20 @@ "type": "tidelift" } ], - "time": "2022-04-22T08:14:12+00:00" + "time": "2022-05-17T15:07:29+00:00" }, { "name": "symfony/http-kernel", - "version": "v5.4.8", + "version": "v5.4.9", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "cf7e61106abfc19b305ca0aedc41724ced89a02a" + "reference": "34b121ad3dc761f35fe1346d2f15618f8cbf77f8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/cf7e61106abfc19b305ca0aedc41724ced89a02a", - "reference": "cf7e61106abfc19b305ca0aedc41724ced89a02a", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/34b121ad3dc761f35fe1346d2f15618f8cbf77f8", + "reference": "34b121ad3dc761f35fe1346d2f15618f8cbf77f8", "shasum": "" }, "require": { @@ -5585,7 +5582,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v5.4.8" + "source": "https://github.com/symfony/http-kernel/tree/v5.4.9" }, "funding": [ { @@ -5601,20 +5598,20 @@ "type": "tidelift" } ], - "time": "2022-04-27T17:22:21+00:00" + "time": "2022-05-27T07:09:08+00:00" }, { "name": "symfony/mime", - "version": "v5.4.8", + "version": "v5.4.9", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "af49bc163ec3272f677bde3bc44c0d766c1fd662" + "reference": "2b3802a24e48d0cfccf885173d2aac91e73df92e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/af49bc163ec3272f677bde3bc44c0d766c1fd662", - "reference": "af49bc163ec3272f677bde3bc44c0d766c1fd662", + "url": "https://api.github.com/repos/symfony/mime/zipball/2b3802a24e48d0cfccf885173d2aac91e73df92e", + "reference": "2b3802a24e48d0cfccf885173d2aac91e73df92e", "shasum": "" }, "require": { @@ -5668,7 +5665,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v5.4.8" + "source": "https://github.com/symfony/mime/tree/v5.4.9" }, "funding": [ { @@ -5684,7 +5681,7 @@ "type": "tidelift" } ], - "time": "2022-04-12T15:48:08+00:00" + "time": "2022-05-21T10:24:18+00:00" }, { "name": "symfony/polyfill-ctype", @@ -6808,16 +6805,16 @@ }, { "name": "symfony/string", - "version": "v5.4.8", + "version": "v5.4.9", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "3c061a76bff6d6ea427d85e12ad1bb8ed8cd43e8" + "reference": "985e6a9703ef5ce32ba617c9c7d97873bb7b2a99" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/3c061a76bff6d6ea427d85e12ad1bb8ed8cd43e8", - "reference": "3c061a76bff6d6ea427d85e12ad1bb8ed8cd43e8", + "url": "https://api.github.com/repos/symfony/string/zipball/985e6a9703ef5ce32ba617c9c7d97873bb7b2a99", + "reference": "985e6a9703ef5ce32ba617c9c7d97873bb7b2a99", "shasum": "" }, "require": { @@ -6874,7 +6871,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v5.4.8" + "source": "https://github.com/symfony/string/tree/v5.4.9" }, "funding": [ { @@ -6894,16 +6891,16 @@ }, { "name": "symfony/translation", - "version": "v5.4.8", + "version": "v5.4.9", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "f5c0f6d1f20993b2606f3a5f36b1dc8c1899170b" + "reference": "1639abc1177d26bcd4320e535e664cef067ab0ca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/f5c0f6d1f20993b2606f3a5f36b1dc8c1899170b", - "reference": "f5c0f6d1f20993b2606f3a5f36b1dc8c1899170b", + "url": "https://api.github.com/repos/symfony/translation/zipball/1639abc1177d26bcd4320e535e664cef067ab0ca", + "reference": "1639abc1177d26bcd4320e535e664cef067ab0ca", "shasum": "" }, "require": { @@ -6971,7 +6968,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v5.4.8" + "source": "https://github.com/symfony/translation/tree/v5.4.9" }, "funding": [ { @@ -6987,7 +6984,7 @@ "type": "tidelift" } ], - "time": "2022-04-22T08:14:12+00:00" + "time": "2022-05-06T12:33:37+00:00" }, { "name": "symfony/translation-contracts", @@ -7069,16 +7066,16 @@ }, { "name": "symfony/var-dumper", - "version": "v5.4.8", + "version": "v5.4.9", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "cdcadd343d31ad16fc5e006b0de81ea307435053" + "reference": "af52239a330fafd192c773795520dc2dd62b5657" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/cdcadd343d31ad16fc5e006b0de81ea307435053", - "reference": "cdcadd343d31ad16fc5e006b0de81ea307435053", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/af52239a330fafd192c773795520dc2dd62b5657", + "reference": "af52239a330fafd192c773795520dc2dd62b5657", "shasum": "" }, "require": { @@ -7138,7 +7135,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v5.4.8" + "source": "https://github.com/symfony/var-dumper/tree/v5.4.9" }, "funding": [ { @@ -7154,7 +7151,7 @@ "type": "tidelift" } ], - "time": "2022-04-26T13:19:20+00:00" + "time": "2022-05-21T10:24:18+00:00" }, { "name": "symfony/yaml", @@ -10727,16 +10724,16 @@ }, { "name": "symfony/filesystem", - "version": "v5.4.7", + "version": "v5.4.9", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "3a4442138d80c9f7b600fb297534ac718b61d37f" + "reference": "36a017fa4cce1eff1b8e8129ff53513abcef05ba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/3a4442138d80c9f7b600fb297534ac718b61d37f", - "reference": "3a4442138d80c9f7b600fb297534ac718b61d37f", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/36a017fa4cce1eff1b8e8129ff53513abcef05ba", + "reference": "36a017fa4cce1eff1b8e8129ff53513abcef05ba", "shasum": "" }, "require": { @@ -10771,7 +10768,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v5.4.7" + "source": "https://github.com/symfony/filesystem/tree/v5.4.9" }, "funding": [ { @@ -10787,7 +10784,7 @@ "type": "tidelift" } ], - "time": "2022-04-01T12:33:59+00:00" + "time": "2022-05-20T13:55:35+00:00" }, { "name": "symfony/options-resolver", From 025e1a21ae51e1eb6fe74463bd18faafbbdc180f Mon Sep 17 00:00:00 2001 From: Boy132 Date: Mon, 30 May 2022 16:24:59 +0200 Subject: [PATCH 074/458] fix validator import (#4094) --- .../Requests/Api/Application/Servers/StoreServerRequest.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/Http/Requests/Api/Application/Servers/StoreServerRequest.php b/app/Http/Requests/Api/Application/Servers/StoreServerRequest.php index dd24e1efc5..681d7bb38f 100644 --- a/app/Http/Requests/Api/Application/Servers/StoreServerRequest.php +++ b/app/Http/Requests/Api/Application/Servers/StoreServerRequest.php @@ -4,8 +4,8 @@ use Pterodactyl\Models\Server; use Illuminate\Validation\Rule; +use Illuminate\Validation\Validator; use Pterodactyl\Services\Acl\Api\AdminAcl; -use Illuminate\Contracts\Validation\Validator; use Pterodactyl\Models\Objects\DeploymentObject; use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest; @@ -108,9 +108,9 @@ public function validated() /* * Run validation after the rules above have been applied. * - * @param \Illuminate\Contracts\Validation\Validator $validator + * @param \Illuminate\Validation\Validator $validator */ - public function withValidator(Validator $validator) + public function withValidator(Validator $validator): void { $validator->sometimes('allocation.default', [ 'required', 'integer', 'bail', From dbc98463209b185d5936cce30bfabe8cdb631832 Mon Sep 17 00:00:00 2001 From: DaneEveritt Date: Mon, 30 May 2022 11:09:51 -0400 Subject: [PATCH 075/458] Fix cache busting when creating a new server --- resources/views/admin/servers/new.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/views/admin/servers/new.blade.php b/resources/views/admin/servers/new.blade.php index 325e61e8eb..51b1b3cca9 100644 --- a/resources/views/admin/servers/new.blade.php +++ b/resources/views/admin/servers/new.blade.php @@ -343,7 +343,7 @@ function serviceVariablesUpdated(eggId, ids) { // END Persist 'Service Variables' - {!! Theme::js('js/admin/new-server.js?v=20201225') !!} + {!! Theme::js('js/admin/new-server.js?v=20220530') !!} ' . PHP_EOL, $this->getUrl($path)); } - public function css($path) + public function css($path): string { return sprintf('' . PHP_EOL, $this->getUrl($path)); } - protected function getUrl($path) + protected function getUrl($path): string { return '/themes/pterodactyl/' . ltrim($path, '/'); } diff --git a/app/Facades/Activity.php b/app/Facades/Activity.php index 9297153af7..5640210011 100644 --- a/app/Facades/Activity.php +++ b/app/Facades/Activity.php @@ -7,7 +7,7 @@ class Activity extends Facade { - protected static function getFacadeAccessor() + protected static function getFacadeAccessor(): string { return ActivityLogService::class; } diff --git a/app/Facades/LogBatch.php b/app/Facades/LogBatch.php index fedfde125f..03d0de57af 100644 --- a/app/Facades/LogBatch.php +++ b/app/Facades/LogBatch.php @@ -3,12 +3,12 @@ namespace Pterodactyl\Facades; use Illuminate\Support\Facades\Facade; -use Pterodactyl\Services\Activity\AcitvityLogBatchService; +use Pterodactyl\Services\Activity\ActivityLogBatchService; class LogBatch extends Facade { - protected static function getFacadeAccessor() + protected static function getFacadeAccessor(): string { - return AcitvityLogBatchService::class; + return ActivityLogBatchService::class; } } diff --git a/app/Facades/LogTarget.php b/app/Facades/LogTarget.php index 18e356e60a..9ea2360020 100644 --- a/app/Facades/LogTarget.php +++ b/app/Facades/LogTarget.php @@ -7,7 +7,7 @@ class LogTarget extends Facade { - protected static function getFacadeAccessor() + protected static function getFacadeAccessor(): string { return ActivityLogTargetableService::class; } diff --git a/app/Helpers/Time.php b/app/Helpers/Time.php index fd9a265a35..e8e585c2bb 100644 --- a/app/Helpers/Time.php +++ b/app/Helpers/Time.php @@ -17,6 +17,6 @@ public static function getMySQLTimezoneOffset(string $timezone): string { $offset = round(CarbonImmutable::now($timezone)->getTimezone()->getOffset(CarbonImmutable::now('UTC')) / 3600); - return sprintf('%s%s:00', $offset > 0 ? '+' : '-', str_pad(abs($offset), 2, '0', STR_PAD_LEFT)); + return sprintf('%s%s:00', $offset > 0 ? '+' : '-', str_pad((string) abs($offset), 2, '0', STR_PAD_LEFT)); } } diff --git a/app/Helpers/Utilities.php b/app/Helpers/Utilities.php index b48e9f0916..f6341c03d6 100644 --- a/app/Helpers/Utilities.php +++ b/app/Helpers/Utilities.php @@ -12,7 +12,7 @@ class Utilities { /** * Generates a random string and injects special characters into it, in addition to - * the randomness of the alpha-numeric default response. + * the randomness of the alphanumeric default response. */ public static function randomStringWithSpecialCharacters(int $length = 16): string { @@ -36,21 +36,16 @@ public static function randomStringWithSpecialCharacters(int $length = 16): stri /** * Converts schedule cron data into a carbon object. * - * @return \Carbon\Carbon + * @throws \Exception */ - public static function getScheduleNextRunDate(string $minute, string $hour, string $dayOfMonth, string $month, string $dayOfWeek) + public static function getScheduleNextRunDate(string $minute, string $hour, string $dayOfMonth, string $month, string $dayOfWeek): Carbon { - return Carbon::instance(CronExpression::factory( + return Carbon::instance((new CronExpression( sprintf('%s %s %s %s %s', $minute, $hour, $dayOfMonth, $month, $dayOfWeek) - )->getNextRunDate()); + ))->getNextRunDate()); } - /** - * @param mixed $default - * - * @return string - */ - public static function checked(string $name, $default) + public static function checked(string $name, mixed $default): string { $errors = session('errors'); diff --git a/app/Http/Controllers/Admin/ApiController.php b/app/Http/Controllers/Admin/ApiController.php index ef9c863558..02ad6e540d 100644 --- a/app/Http/Controllers/Admin/ApiController.php +++ b/app/Http/Controllers/Admin/ApiController.php @@ -9,6 +9,7 @@ use Illuminate\Http\RedirectResponse; use Prologue\Alerts\AlertsMessageBag; use Pterodactyl\Services\Acl\Api\AdminAcl; +use Illuminate\View\Factory as ViewFactory; use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Services\Api\KeyCreationService; use Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface; @@ -17,31 +18,14 @@ class ApiController extends Controller { /** - * @var \Prologue\Alerts\AlertsMessageBag - */ - private $alert; - - /** - * @var \Pterodactyl\Services\Api\KeyCreationService - */ - private $keyCreationService; - - /** - * @var \Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface - */ - private $repository; - - /** - * ApplicationApiController constructor. + * ApiController constructor. */ public function __construct( - AlertsMessageBag $alert, - ApiKeyRepositoryInterface $repository, - KeyCreationService $keyCreationService + private AlertsMessageBag $alert, + private ApiKeyRepositoryInterface $repository, + private KeyCreationService $keyCreationService, + private ViewFactory $view, ) { - $this->alert = $alert; - $this->keyCreationService = $keyCreationService; - $this->repository = $repository; } /** @@ -49,7 +33,7 @@ public function __construct( */ public function index(Request $request): View { - return view('admin.api.index', [ + return $this->view->make('admin.api.index', [ 'keys' => $this->repository->getApplicationKeys($request->user()), ]); } @@ -64,7 +48,7 @@ public function create(): View $resources = AdminAcl::getResourceList(); sort($resources); - return view('admin.api.new', [ + return $this->view->make('admin.api.new', [ 'resources' => $resources, 'permissions' => [ 'r' => AdminAcl::READ, diff --git a/app/Http/Controllers/Admin/BaseController.php b/app/Http/Controllers/Admin/BaseController.php index 68e923ad8c..53f53ce54c 100644 --- a/app/Http/Controllers/Admin/BaseController.php +++ b/app/Http/Controllers/Admin/BaseController.php @@ -3,22 +3,17 @@ namespace Pterodactyl\Http\Controllers\Admin; use Illuminate\View\View; +use Illuminate\View\Factory as ViewFactory; use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Services\Helpers\SoftwareVersionService; class BaseController extends Controller { - /** - * @var \Pterodactyl\Services\Helpers\SoftwareVersionService - */ - private $version; - /** * BaseController constructor. */ - public function __construct(SoftwareVersionService $version) + public function __construct(private SoftwareVersionService $version, private ViewFactory $view) { - $this->version = $version; } /** @@ -26,6 +21,6 @@ public function __construct(SoftwareVersionService $version) */ public function index(): View { - return view('admin.index', ['version' => $this->version]); + return $this->view->make('admin.index', ['version' => $this->version]); } } diff --git a/app/Http/Controllers/Admin/DatabaseController.php b/app/Http/Controllers/Admin/DatabaseController.php index 9e6cc000a4..e0dc0dc577 100644 --- a/app/Http/Controllers/Admin/DatabaseController.php +++ b/app/Http/Controllers/Admin/DatabaseController.php @@ -8,6 +8,7 @@ use Pterodactyl\Models\DatabaseHost; use Illuminate\Http\RedirectResponse; use Prologue\Alerts\AlertsMessageBag; +use Illuminate\View\Factory as ViewFactory; use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Services\Databases\Hosts\HostUpdateService; use Pterodactyl\Http\Requests\Admin\DatabaseHostFormRequest; @@ -19,60 +20,19 @@ class DatabaseController extends Controller { - /** - * @var \Prologue\Alerts\AlertsMessageBag - */ - private $alert; - - /** - * @var \Pterodactyl\Services\Databases\Hosts\HostCreationService - */ - private $creationService; - - /** - * @var \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface - */ - private $databaseRepository; - - /** - * @var \Pterodactyl\Services\Databases\Hosts\HostDeletionService - */ - private $deletionService; - - /** - * @var \Pterodactyl\Contracts\Repository\LocationRepositoryInterface - */ - private $locationRepository; - - /** - * @var \Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface - */ - private $repository; - - /** - * @var \Pterodactyl\Services\Databases\Hosts\HostUpdateService - */ - private $updateService; - /** * DatabaseController constructor. */ public function __construct( - AlertsMessageBag $alert, - DatabaseHostRepositoryInterface $repository, - DatabaseRepositoryInterface $databaseRepository, - HostCreationService $creationService, - HostDeletionService $deletionService, - HostUpdateService $updateService, - LocationRepositoryInterface $locationRepository + private AlertsMessageBag $alert, + private DatabaseHostRepositoryInterface $repository, + private DatabaseRepositoryInterface $databaseRepository, + private HostCreationService $creationService, + private HostDeletionService $deletionService, + private HostUpdateService $updateService, + private LocationRepositoryInterface $locationRepository, + private ViewFactory $view ) { - $this->alert = $alert; - $this->creationService = $creationService; - $this->databaseRepository = $databaseRepository; - $this->deletionService = $deletionService; - $this->repository = $repository; - $this->locationRepository = $locationRepository; - $this->updateService = $updateService; } /** @@ -80,7 +40,7 @@ public function __construct( */ public function index(): View { - return view('admin.databases.index', [ + return $this->view->make('admin.databases.index', [ 'locations' => $this->locationRepository->getAllWithNodes(), 'hosts' => $this->repository->getWithViewDetails(), ]); @@ -93,7 +53,7 @@ public function index(): View */ public function view(int $host): View { - return view('admin.databases.view', [ + return $this->view->make('admin.databases.view', [ 'locations' => $this->locationRepository->getAllWithNodes(), 'host' => $this->repository->find($host), 'databases' => $this->databaseRepository->getDatabasesForHost($host), diff --git a/app/Http/Controllers/Admin/LocationController.php b/app/Http/Controllers/Admin/LocationController.php index 195977f27c..ea01cbaa90 100644 --- a/app/Http/Controllers/Admin/LocationController.php +++ b/app/Http/Controllers/Admin/LocationController.php @@ -1,16 +1,12 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Http\Controllers\Admin; +use Illuminate\View\View; use Pterodactyl\Models\Location; +use Illuminate\Http\RedirectResponse; use Prologue\Alerts\AlertsMessageBag; +use Illuminate\View\Factory as ViewFactory; use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Http\Requests\Admin\LocationFormRequest; @@ -21,56 +17,25 @@ class LocationController extends Controller { - /** - * @var \Prologue\Alerts\AlertsMessageBag - */ - protected $alert; - - /** - * @var \Pterodactyl\Services\Locations\LocationCreationService - */ - protected $creationService; - - /** - * @var \Pterodactyl\Services\Locations\LocationDeletionService - */ - protected $deletionService; - - /** - * @var \Pterodactyl\Contracts\Repository\LocationRepositoryInterface - */ - protected $repository; - - /** - * @var \Pterodactyl\Services\Locations\LocationUpdateService - */ - protected $updateService; - /** * LocationController constructor. */ public function __construct( - AlertsMessageBag $alert, - LocationCreationService $creationService, - LocationDeletionService $deletionService, - LocationRepositoryInterface $repository, - LocationUpdateService $updateService + protected AlertsMessageBag $alert, + protected LocationCreationService $creationService, + protected LocationDeletionService $deletionService, + protected LocationRepositoryInterface $repository, + protected LocationUpdateService $updateService, + protected ViewFactory $view ) { - $this->alert = $alert; - $this->creationService = $creationService; - $this->deletionService = $deletionService; - $this->repository = $repository; - $this->updateService = $updateService; } /** * Return the location overview page. - * - * @return \Illuminate\View\View */ - public function index() + public function index(): View { - return view('admin.locations.index', [ + return $this->view->make('admin.locations.index', [ 'locations' => $this->repository->getAllWithDetails(), ]); } @@ -78,15 +43,11 @@ public function index() /** * Return the location view page. * - * @param int $id - * - * @return \Illuminate\View\View - * * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function view($id) + public function view(int $id): View { - return view('admin.locations.view', [ + return $this->view->make('admin.locations.view', [ 'location' => $this->repository->getWithNodes($id), ]); } @@ -94,11 +55,9 @@ public function view($id) /** * Handle request to create new location. * - * @return \Illuminate\Http\RedirectResponse - * * @throws \Throwable */ - public function create(LocationFormRequest $request) + public function create(LocationFormRequest $request): RedirectResponse { $location = $this->creationService->handle($request->normalize()); $this->alert->success('Location was created successfully.')->flash(); @@ -109,11 +68,9 @@ public function create(LocationFormRequest $request) /** * Handle request to update or delete location. * - * @return \Illuminate\Http\RedirectResponse - * * @throws \Throwable */ - public function update(LocationFormRequest $request, Location $location) + public function update(LocationFormRequest $request, Location $location): RedirectResponse { if ($request->input('action') === 'delete') { return $this->delete($location); @@ -128,12 +85,10 @@ public function update(LocationFormRequest $request, Location $location) /** * Delete a location from the system. * - * @return \Illuminate\Http\RedirectResponse - * * @throws \Exception * @throws \Pterodactyl\Exceptions\DisplayException */ - public function delete(Location $location) + public function delete(Location $location): RedirectResponse { try { $this->deletionService->handle($location->id); diff --git a/app/Http/Controllers/Admin/MountController.php b/app/Http/Controllers/Admin/MountController.php index 811abfba24..097ad6690b 100644 --- a/app/Http/Controllers/Admin/MountController.php +++ b/app/Http/Controllers/Admin/MountController.php @@ -3,11 +3,15 @@ namespace Pterodactyl\Http\Controllers\Admin; use Ramsey\Uuid\Uuid; +use Illuminate\View\View; use Illuminate\Http\Request; use Pterodactyl\Models\Nest; +use Illuminate\Http\Response; use Pterodactyl\Models\Mount; use Pterodactyl\Models\Location; +use Illuminate\Http\RedirectResponse; use Prologue\Alerts\AlertsMessageBag; +use Illuminate\View\Factory as ViewFactory; use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Http\Requests\Admin\MountFormRequest; use Pterodactyl\Repositories\Eloquent\MountRepository; @@ -16,49 +20,24 @@ class MountController extends Controller { - /** - * @var \Prologue\Alerts\AlertsMessageBag - */ - protected $alert; - - /** - * @var \Pterodactyl\Contracts\Repository\NestRepositoryInterface - */ - protected $nestRepository; - - /** - * @var \Pterodactyl\Contracts\Repository\LocationRepositoryInterface - */ - protected $locationRepository; - - /** - * @var \Pterodactyl\Repositories\Eloquent\MountRepository - */ - protected $repository; - /** * MountController constructor. */ public function __construct( - AlertsMessageBag $alert, - NestRepositoryInterface $nestRepository, - LocationRepositoryInterface $locationRepository, - MountRepository $repository + protected AlertsMessageBag $alert, + protected NestRepositoryInterface $nestRepository, + protected LocationRepositoryInterface $locationRepository, + protected MountRepository $repository, + protected ViewFactory $view ) { - $this->alert = $alert; - $this->nestRepository = $nestRepository; - $this->locationRepository = $locationRepository; - $this->repository = $repository; } /** * Return the mount overview page. - * - * @return \Illuminate\View\View */ - public function index() + public function index(): View { - return view('admin.mounts.index', [ + return $this->view->make('admin.mounts.index', [ 'mounts' => $this->repository->getAllWithDetails(), ]); } @@ -66,18 +45,14 @@ public function index() /** * Return the mount view page. * - * @param string $id - * - * @return \Illuminate\View\View - * * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function view($id) + public function view(string $id): View { $nests = Nest::query()->with('eggs')->get(); $locations = Location::query()->with('nodes')->get(); - return view('admin.mounts.view', [ + return $this->view->make('admin.mounts.view', [ 'mount' => $this->repository->getWithRelations($id), 'nests' => $nests, 'locations' => $locations, @@ -87,11 +62,9 @@ public function view($id) /** * Handle request to create new mount. * - * @return \Illuminate\Http\RedirectResponse - * * @throws \Throwable */ - public function create(MountFormRequest $request) + public function create(MountFormRequest $request): RedirectResponse { $model = (new Mount())->fill($request->validated()); $model->forceFill(['uuid' => Uuid::uuid4()->toString()]); @@ -107,11 +80,9 @@ public function create(MountFormRequest $request) /** * Handle request to update or delete location. * - * @return \Illuminate\Http\RedirectResponse - * * @throws \Throwable */ - public function update(MountFormRequest $request, Mount $mount) + public function update(MountFormRequest $request, Mount $mount): RedirectResponse { if ($request->input('action') === 'delete') { return $this->delete($mount); @@ -127,11 +98,9 @@ public function update(MountFormRequest $request, Mount $mount) /** * Delete a location from the system. * - * @return \Illuminate\Http\RedirectResponse - * * @throws \Exception */ - public function delete(Mount $mount) + public function delete(Mount $mount): RedirectResponse { $mount->delete(); @@ -139,11 +108,9 @@ public function delete(Mount $mount) } /** - * Adds eggs to the mount's many to many relation. - * - * @return \Illuminate\Http\RedirectResponse + * Adds eggs to the mount's many-to-many relation. */ - public function addEggs(Request $request, Mount $mount) + public function addEggs(Request $request, Mount $mount): RedirectResponse { $validatedData = $request->validate([ 'eggs' => 'required|exists:eggs,id', @@ -160,11 +127,9 @@ public function addEggs(Request $request, Mount $mount) } /** - * Adds nodes to the mount's many to many relation. - * - * @return \Illuminate\Http\RedirectResponse + * Adds nodes to the mount's many-to-many relation. */ - public function addNodes(Request $request, Mount $mount) + public function addNodes(Request $request, Mount $mount): RedirectResponse { $data = $request->validate(['nodes' => 'required|exists:nodes,id']); @@ -179,11 +144,9 @@ public function addNodes(Request $request, Mount $mount) } /** - * Deletes an egg from the mount's many to many relation. - * - * @return \Illuminate\Http\Response + * Deletes an egg from the mount's many-to-many relation. */ - public function deleteEgg(Mount $mount, int $egg_id) + public function deleteEgg(Mount $mount, int $egg_id): Response { $mount->eggs()->detach($egg_id); @@ -191,11 +154,9 @@ public function deleteEgg(Mount $mount, int $egg_id) } /** - * Deletes an node from the mount's many to many relation. - * - * @return \Illuminate\Http\Response + * Deletes a node from the mount's many-to-many relation. */ - public function deleteNode(Mount $mount, int $node_id) + public function deleteNode(Mount $mount, int $node_id): Response { $mount->nodes()->detach($node_id); diff --git a/app/Http/Controllers/Admin/Nests/EggController.php b/app/Http/Controllers/Admin/Nests/EggController.php index a67c51311b..010c28af00 100644 --- a/app/Http/Controllers/Admin/Nests/EggController.php +++ b/app/Http/Controllers/Admin/Nests/EggController.php @@ -1,19 +1,13 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Http\Controllers\Admin\Nests; -use Javascript; +use JavaScript; use Illuminate\View\View; use Pterodactyl\Models\Egg; use Illuminate\Http\RedirectResponse; use Prologue\Alerts\AlertsMessageBag; +use Illuminate\View\Factory as ViewFactory; use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Services\Eggs\EggUpdateService; use Pterodactyl\Services\Eggs\EggCreationService; @@ -24,32 +18,18 @@ class EggController extends Controller { - protected $alert; - - protected $creationService; - - protected $deletionService; - - protected $nestRepository; - - protected $repository; - - protected $updateService; - + /** + * EggController constructor. + */ public function __construct( - AlertsMessageBag $alert, - EggCreationService $creationService, - EggDeletionService $deletionService, - EggRepositoryInterface $repository, - EggUpdateService $updateService, - NestRepositoryInterface $nestRepository + protected AlertsMessageBag $alert, + protected EggCreationService $creationService, + protected EggDeletionService $deletionService, + protected EggRepositoryInterface $repository, + protected EggUpdateService $updateService, + protected NestRepositoryInterface $nestRepository, + protected ViewFactory $view ) { - $this->alert = $alert; - $this->creationService = $creationService; - $this->deletionService = $deletionService; - $this->nestRepository = $nestRepository; - $this->repository = $repository; - $this->updateService = $updateService; } /** @@ -60,9 +40,9 @@ public function __construct( public function create(): View { $nests = $this->nestRepository->getWithEggs(); - Javascript::put(['nests' => $nests->keyBy('id')]); + JavaScript::put(['nests' => $nests->keyBy('id')]); - return view('admin.eggs.new', ['nests' => $nests]); + return $this->view->make('admin.eggs.new', ['nests' => $nests]); } /** @@ -87,7 +67,7 @@ public function store(EggFormRequest $request): RedirectResponse */ public function view(Egg $egg): View { - return view('admin.eggs.view', [ + return $this->view->make('admin.eggs.view', [ 'egg' => $egg, 'images' => array_map( fn ($key, $value) => $key === $value ? $value : "$key|$value", diff --git a/app/Http/Controllers/Admin/Nests/EggScriptController.php b/app/Http/Controllers/Admin/Nests/EggScriptController.php index 4ae8f410b4..4f997e5a86 100644 --- a/app/Http/Controllers/Admin/Nests/EggScriptController.php +++ b/app/Http/Controllers/Admin/Nests/EggScriptController.php @@ -6,6 +6,7 @@ use Pterodactyl\Models\Egg; use Illuminate\Http\RedirectResponse; use Prologue\Alerts\AlertsMessageBag; +use Illuminate\View\Factory as ViewFactory; use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Services\Eggs\Scripts\InstallScriptService; use Pterodactyl\Contracts\Repository\EggRepositoryInterface; @@ -13,32 +14,15 @@ class EggScriptController extends Controller { - /** - * @var \Prologue\Alerts\AlertsMessageBag - */ - protected $alert; - - /** - * @var \Pterodactyl\Services\Eggs\Scripts\InstallScriptService - */ - protected $installScriptService; - - /** - * @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface - */ - protected $repository; - /** * EggScriptController constructor. */ public function __construct( - AlertsMessageBag $alert, - EggRepositoryInterface $repository, - InstallScriptService $installScriptService + protected AlertsMessageBag $alert, + protected EggRepositoryInterface $repository, + protected InstallScriptService $installScriptService, + protected ViewFactory $view ) { - $this->alert = $alert; - $this->installScriptService = $installScriptService; - $this->repository = $repository; } /** @@ -57,7 +41,7 @@ public function index(int $egg): View ['copy_script_from', '=', $egg->id], ]); - return view('admin.eggs.scripts', [ + return $this->view->make('admin.eggs.scripts', [ 'copyFromOptions' => $copy, 'relyOnScript' => $rely, 'egg' => $egg, diff --git a/app/Http/Controllers/Admin/Nests/EggShareController.php b/app/Http/Controllers/Admin/Nests/EggShareController.php index 32882ba5c2..b2c704e52a 100644 --- a/app/Http/Controllers/Admin/Nests/EggShareController.php +++ b/app/Http/Controllers/Admin/Nests/EggShareController.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Http\Controllers\Admin\Nests; @@ -22,38 +15,14 @@ class EggShareController extends Controller { /** - * @var \Prologue\Alerts\AlertsMessageBag - */ - protected $alert; - - /** - * @var \Pterodactyl\Services\Eggs\Sharing\EggExporterService - */ - protected $exporterService; - - /** - * @var \Pterodactyl\Services\Eggs\Sharing\EggImporterService - */ - protected $importerService; - - /** - * @var \Pterodactyl\Services\Eggs\Sharing\EggUpdateImporterService - */ - protected $updateImporterService; - - /** - * OptionShareController constructor. + * EggShareController constructor. */ public function __construct( - AlertsMessageBag $alert, - EggExporterService $exporterService, - EggImporterService $importerService, - EggUpdateImporterService $updateImporterService + protected AlertsMessageBag $alert, + protected EggExporterService $exporterService, + protected EggImporterService $importerService, + protected EggUpdateImporterService $updateImporterService ) { - $this->alert = $alert; - $this->exporterService = $exporterService; - $this->importerService = $importerService; - $this->updateImporterService = $updateImporterService; } /** @@ -61,7 +30,7 @@ public function __construct( */ public function export(Egg $egg): Response { - $filename = trim(preg_replace('/[^\w]/', '-', kebab_case($egg->name)), '-'); + $filename = trim(preg_replace('/\W/', '-', kebab_case($egg->name)), '-'); return response($this->exporterService->handle($egg->id), 200, [ 'Content-Transfer-Encoding' => 'binary', diff --git a/app/Http/Controllers/Admin/Nests/EggVariableController.php b/app/Http/Controllers/Admin/Nests/EggVariableController.php index 193f3b9b53..40274b3239 100644 --- a/app/Http/Controllers/Admin/Nests/EggVariableController.php +++ b/app/Http/Controllers/Admin/Nests/EggVariableController.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Http\Controllers\Admin\Nests; @@ -14,6 +7,7 @@ use Pterodactyl\Models\EggVariable; use Illuminate\Http\RedirectResponse; use Prologue\Alerts\AlertsMessageBag; +use Illuminate\View\Factory as ViewFactory; use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Contracts\Repository\EggRepositoryInterface; use Pterodactyl\Services\Eggs\Variables\VariableUpdateService; @@ -23,46 +17,17 @@ class EggVariableController extends Controller { - /** - * @var \Prologue\Alerts\AlertsMessageBag - */ - protected $alert; - - /** - * @var \Pterodactyl\Services\Eggs\Variables\VariableCreationService - */ - protected $creationService; - - /** - * @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface - */ - protected $repository; - - /** - * @var \Pterodactyl\Services\Eggs\Variables\VariableUpdateService - */ - protected $updateService; - - /** - * @var \Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface - */ - protected $variableRepository; - /** * EggVariableController constructor. */ public function __construct( - AlertsMessageBag $alert, - VariableCreationService $creationService, - VariableUpdateService $updateService, - EggRepositoryInterface $repository, - EggVariableRepositoryInterface $variableRepository + protected AlertsMessageBag $alert, + protected VariableCreationService $creationService, + protected VariableUpdateService $updateService, + protected EggRepositoryInterface $repository, + protected EggVariableRepositoryInterface $variableRepository, + protected ViewFactory $view ) { - $this->alert = $alert; - $this->creationService = $creationService; - $this->repository = $repository; - $this->updateService = $updateService; - $this->variableRepository = $variableRepository; } /** @@ -74,7 +39,7 @@ public function view(int $egg): View { $egg = $this->repository->getWithVariables($egg); - return view('admin.eggs.variables', ['egg' => $egg]); + return $this->view->make('admin.eggs.variables', ['egg' => $egg]); } /** diff --git a/app/Http/Controllers/Admin/Nests/NestController.php b/app/Http/Controllers/Admin/Nests/NestController.php index 89e1efd777..037dd09430 100644 --- a/app/Http/Controllers/Admin/Nests/NestController.php +++ b/app/Http/Controllers/Admin/Nests/NestController.php @@ -1,17 +1,11 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Http\Controllers\Admin\Nests; use Illuminate\View\View; use Illuminate\Http\RedirectResponse; use Prologue\Alerts\AlertsMessageBag; +use Illuminate\View\Factory as ViewFactory; use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Services\Nests\NestUpdateService; use Pterodactyl\Services\Nests\NestCreationService; @@ -21,46 +15,17 @@ class NestController extends Controller { - /** - * @var \Prologue\Alerts\AlertsMessageBag - */ - protected $alert; - - /** - * @var \Pterodactyl\Services\Nests\NestCreationService - */ - protected $nestCreationService; - - /** - * @var \Pterodactyl\Services\Nests\NestDeletionService - */ - protected $nestDeletionService; - - /** - * @var \Pterodactyl\Contracts\Repository\NestRepositoryInterface - */ - protected $repository; - - /** - * @var \Pterodactyl\Services\Nests\NestUpdateService - */ - protected $nestUpdateService; - /** * NestController constructor. */ public function __construct( - AlertsMessageBag $alert, - NestCreationService $nestCreationService, - NestDeletionService $nestDeletionService, - NestRepositoryInterface $repository, - NestUpdateService $nestUpdateService + protected AlertsMessageBag $alert, + protected NestCreationService $nestCreationService, + protected NestDeletionService $nestDeletionService, + protected NestRepositoryInterface $repository, + protected NestUpdateService $nestUpdateService, + protected ViewFactory $view ) { - $this->alert = $alert; - $this->nestDeletionService = $nestDeletionService; - $this->nestCreationService = $nestCreationService; - $this->nestUpdateService = $nestUpdateService; - $this->repository = $repository; } /** @@ -70,7 +35,7 @@ public function __construct( */ public function index(): View { - return view('admin.nests.index', [ + return $this->view->make('admin.nests.index', [ 'nests' => $this->repository->getWithCounts(), ]); } @@ -80,7 +45,7 @@ public function index(): View */ public function create(): View { - return view('admin.nests.new'); + return $this->view->make('admin.nests.new'); } /** @@ -97,13 +62,13 @@ public function store(StoreNestFormRequest $request): RedirectResponse } /** - * Return details about a nest including all of the eggs and servers per egg. + * Return details about a nest including all the eggs and servers per egg. * * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ public function view(int $nest): View { - return view('admin.nests.view', [ + return $this->view->make('admin.nests.view', [ 'nest' => $this->repository->getWithEggServers($nest), ]); } diff --git a/app/Http/Controllers/Admin/NodeAutoDeployController.php b/app/Http/Controllers/Admin/NodeAutoDeployController.php index 79305a38b8..ac0684a9c9 100644 --- a/app/Http/Controllers/Admin/NodeAutoDeployController.php +++ b/app/Http/Controllers/Admin/NodeAutoDeployController.php @@ -13,43 +13,23 @@ class NodeAutoDeployController extends Controller { - /** - * @var \Pterodactyl\Services\Api\KeyCreationService - */ - private $keyCreationService; - - /** - * @var \Pterodactyl\Repositories\Eloquent\ApiKeyRepository - */ - private $repository; - - /** - * @var \Illuminate\Contracts\Encryption\Encrypter - */ - private $encrypter; - /** * NodeAutoDeployController constructor. */ public function __construct( - ApiKeyRepository $repository, - Encrypter $encrypter, - KeyCreationService $keyCreationService + private ApiKeyRepository $repository, + private Encrypter $encrypter, + private KeyCreationService $keyCreationService ) { - $this->keyCreationService = $keyCreationService; - $this->repository = $repository; - $this->encrypter = $encrypter; } /** - * Generates a new API key for the logged in user with only permission to read + * Generates a new API key for the logged-in user with only permission to read * nodes, and returns that as the deployment key for a node. * - * @return \Illuminate\Http\JsonResponse - * * @throws \Pterodactyl\Exceptions\Model\DataValidationException */ - public function __invoke(Request $request, Node $node) + public function __invoke(Request $request, Node $node): JsonResponse { /** @var \Pterodactyl\Models\ApiKey|null $key */ $key = $this->repository->getApplicationKeys($request->user()) diff --git a/app/Http/Controllers/Admin/Nodes/NodeController.php b/app/Http/Controllers/Admin/Nodes/NodeController.php index d3d53fc8cb..d80df6c80d 100644 --- a/app/Http/Controllers/Admin/Nodes/NodeController.php +++ b/app/Http/Controllers/Admin/Nodes/NodeController.php @@ -2,40 +2,26 @@ namespace Pterodactyl\Http\Controllers\Admin\Nodes; +use Illuminate\View\View; use Illuminate\Http\Request; use Pterodactyl\Models\Node; use Spatie\QueryBuilder\QueryBuilder; -use Illuminate\Contracts\View\Factory; use Pterodactyl\Http\Controllers\Controller; -use Pterodactyl\Repositories\Eloquent\NodeRepository; +use Illuminate\Contracts\View\Factory as ViewFactory; class NodeController extends Controller { - /** - * @var \Illuminate\Contracts\View\Factory - */ - private $view; - - /** - * @var \Pterodactyl\Repositories\Eloquent\NodeRepository - */ - private $repository; - /** * NodeController constructor. */ - public function __construct(NodeRepository $repository, Factory $view) + public function __construct(private ViewFactory $view) { - $this->view = $view; - $this->repository = $repository; } /** * Returns a listing of nodes on the system. - * - * @return \Illuminate\Contracts\View\View */ - public function index(Request $request) + public function index(Request $request): View { $nodes = QueryBuilder::for( Node::query()->with('location')->withCount('servers') diff --git a/app/Http/Controllers/Admin/Nodes/NodeViewController.php b/app/Http/Controllers/Admin/Nodes/NodeViewController.php index b45b23a098..6738903230 100644 --- a/app/Http/Controllers/Admin/Nodes/NodeViewController.php +++ b/app/Http/Controllers/Admin/Nodes/NodeViewController.php @@ -2,12 +2,13 @@ namespace Pterodactyl\Http\Controllers\Admin\Nodes; +use Illuminate\View\View; use Illuminate\Http\Request; use Pterodactyl\Models\Node; use Illuminate\Support\Collection; use Pterodactyl\Models\Allocation; -use Illuminate\Contracts\View\Factory; use Pterodactyl\Http\Controllers\Controller; +use Illuminate\Contracts\View\Factory as ViewFactory; use Pterodactyl\Repositories\Eloquent\NodeRepository; use Pterodactyl\Repositories\Eloquent\ServerRepository; use Pterodactyl\Traits\Controllers\JavascriptInjection; @@ -19,61 +20,23 @@ class NodeViewController extends Controller { use JavascriptInjection; - /** - * @var \Pterodactyl\Repositories\Eloquent\NodeRepository - */ - private $repository; - - /** - * @var \Illuminate\Contracts\View\Factory - */ - private $view; - - /** - * @var \Pterodactyl\Services\Helpers\SoftwareVersionService - */ - private $versionService; - - /** - * @var \Pterodactyl\Repositories\Eloquent\LocationRepository - */ - private $locationRepository; - - /** - * @var \Pterodactyl\Repositories\Eloquent\AllocationRepository - */ - private $allocationRepository; - - /** - * @var \Pterodactyl\Repositories\Eloquent\ServerRepository - */ - private $serverRepository; - /** * NodeViewController constructor. */ public function __construct( - AllocationRepository $allocationRepository, - LocationRepository $locationRepository, - NodeRepository $repository, - ServerRepository $serverRepository, - SoftwareVersionService $versionService, - Factory $view + private AllocationRepository $allocationRepository, + private LocationRepository $locationRepository, + private NodeRepository $repository, + private ServerRepository $serverRepository, + private SoftwareVersionService $versionService, + private ViewFactory $view ) { - $this->repository = $repository; - $this->view = $view; - $this->versionService = $versionService; - $this->locationRepository = $locationRepository; - $this->allocationRepository = $allocationRepository; - $this->serverRepository = $serverRepository; } /** * Returns index view for a specific node on the system. - * - * @return \Illuminate\Contracts\View\View */ - public function index(Request $request, Node $node) + public function index(Request $request, Node $node): View { $node = $this->repository->loadLocationAndServerCount($node); @@ -86,10 +49,8 @@ public function index(Request $request, Node $node) /** * Returns the settings page for a specific node. - * - * @return \Illuminate\Contracts\View\View */ - public function settings(Request $request, Node $node) + public function settings(Request $request, Node $node): View { return $this->view->make('admin.nodes.view.settings', [ 'node' => $node, @@ -99,20 +60,16 @@ public function settings(Request $request, Node $node) /** * Return the node configuration page for a specific node. - * - * @return \Illuminate\Contracts\View\View */ - public function configuration(Request $request, Node $node) + public function configuration(Request $request, Node $node): View { return $this->view->make('admin.nodes.view.configuration', compact('node')); } /** * Return the node allocation management page. - * - * @return \Illuminate\Contracts\View\View */ - public function allocations(Request $request, Node $node) + public function allocations(Request $request, Node $node): View { $node = $this->repository->loadNodeAllocations($node); @@ -129,10 +86,8 @@ public function allocations(Request $request, Node $node) /** * Return a listing of servers that exist for this specific node. - * - * @return \Illuminate\Contracts\View\View */ - public function servers(Request $request, Node $node) + public function servers(Request $request, Node $node): View { $this->plainInject([ 'node' => Collection::wrap($node->makeVisible(['daemon_token_id', 'daemon_token'])) diff --git a/app/Http/Controllers/Admin/Nodes/SystemInformationController.php b/app/Http/Controllers/Admin/Nodes/SystemInformationController.php index 954897a885..875b1afbea 100644 --- a/app/Http/Controllers/Admin/Nodes/SystemInformationController.php +++ b/app/Http/Controllers/Admin/Nodes/SystemInformationController.php @@ -11,27 +11,19 @@ class SystemInformationController extends Controller { - /** - * @var \Pterodactyl\Repositories\Wings\DaemonConfigurationRepository - */ - private $repository; - /** * SystemInformationController constructor. */ - public function __construct(DaemonConfigurationRepository $repository) + public function __construct(private DaemonConfigurationRepository $repository) { - $this->repository = $repository; } /** * Returns system information from the Daemon. * - * @return \Illuminate\Http\JsonResponse - * * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException */ - public function __invoke(Request $request, Node $node) + public function __invoke(Request $request, Node $node): JsonResponse { $data = $this->repository->setNode($node)->getSystemInformation(); diff --git a/app/Http/Controllers/Admin/NodesController.php b/app/Http/Controllers/Admin/NodesController.php index 193326dee0..573a1d9f8d 100644 --- a/app/Http/Controllers/Admin/NodesController.php +++ b/app/Http/Controllers/Admin/NodesController.php @@ -1,19 +1,15 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Http\Controllers\Admin; +use Illuminate\View\View; use Illuminate\Http\Request; use Pterodactyl\Models\Node; use Illuminate\Http\Response; use Pterodactyl\Models\Allocation; +use Illuminate\Http\RedirectResponse; use Prologue\Alerts\AlertsMessageBag; +use Illuminate\View\Factory as ViewFactory; use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Services\Nodes\NodeUpdateService; use Illuminate\Cache\Repository as CacheRepository; @@ -32,103 +28,30 @@ class NodesController extends Controller { - /** - * @var \Pterodactyl\Services\Allocations\AllocationDeletionService - */ - protected $allocationDeletionService; - - /** - * @var \Prologue\Alerts\AlertsMessageBag - */ - protected $alert; - - /** - * @var \Pterodactyl\Contracts\Repository\AllocationRepositoryInterface - */ - protected $allocationRepository; - - /** - * @var \Pterodactyl\Services\Allocations\AssignmentService - */ - protected $assignmentService; - - /** - * @var \Illuminate\Cache\Repository - */ - protected $cache; - - /** - * @var \Pterodactyl\Services\Nodes\NodeCreationService - */ - protected $creationService; - - /** - * @var \Pterodactyl\Services\Nodes\NodeDeletionService - */ - protected $deletionService; - - /** - * @var \Pterodactyl\Contracts\Repository\LocationRepositoryInterface - */ - protected $locationRepository; - - /** - * @var \Pterodactyl\Contracts\Repository\NodeRepositoryInterface - */ - protected $repository; - - /** - * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface - */ - protected $serverRepository; - - /** - * @var \Pterodactyl\Services\Nodes\NodeUpdateService - */ - protected $updateService; - - /** - * @var \Pterodactyl\Services\Helpers\SoftwareVersionService - */ - protected $versionService; - /** * NodesController constructor. */ public function __construct( - AlertsMessageBag $alert, - AllocationDeletionService $allocationDeletionService, - AllocationRepositoryInterface $allocationRepository, - AssignmentService $assignmentService, - CacheRepository $cache, - NodeCreationService $creationService, - NodeDeletionService $deletionService, - LocationRepositoryInterface $locationRepository, - NodeRepositoryInterface $repository, - ServerRepositoryInterface $serverRepository, - NodeUpdateService $updateService, - SoftwareVersionService $versionService + protected AlertsMessageBag $alert, + protected AllocationDeletionService $allocationDeletionService, + protected AllocationRepositoryInterface $allocationRepository, + protected AssignmentService $assignmentService, + protected CacheRepository $cache, + protected NodeCreationService $creationService, + protected NodeDeletionService $deletionService, + protected LocationRepositoryInterface $locationRepository, + protected NodeRepositoryInterface $repository, + protected ServerRepositoryInterface $serverRepository, + protected NodeUpdateService $updateService, + protected SoftwareVersionService $versionService, + protected ViewFactory $view ) { - $this->alert = $alert; - $this->allocationDeletionService = $allocationDeletionService; - $this->allocationRepository = $allocationRepository; - $this->assignmentService = $assignmentService; - $this->cache = $cache; - $this->creationService = $creationService; - $this->deletionService = $deletionService; - $this->locationRepository = $locationRepository; - $this->repository = $repository; - $this->serverRepository = $serverRepository; - $this->updateService = $updateService; - $this->versionService = $versionService; } /** * Displays create new node page. - * - * @return \Illuminate\Http\RedirectResponse|\Illuminate\View\View */ - public function create() + public function create(): View|RedirectResponse { $locations = $this->locationRepository->all(); if (count($locations) < 1) { @@ -137,17 +60,15 @@ public function create() return redirect()->route('admin.locations'); } - return view('admin.nodes.new', ['locations' => $locations]); + return $this->view->make('admin.nodes.new', ['locations' => $locations]); } /** * Post controller to create a new node on the system. * - * @return \Illuminate\Http\RedirectResponse - * * @throws \Pterodactyl\Exceptions\Model\DataValidationException */ - public function store(NodeFormRequest $request) + public function store(NodeFormRequest $request): RedirectResponse { $node = $this->creationService->handle($request->normalize()); $this->alert->info(trans('admin/node.notices.node_created'))->flash(); @@ -158,13 +79,11 @@ public function store(NodeFormRequest $request) /** * Updates settings for a node. * - * @return \Illuminate\Http\RedirectResponse - * * @throws \Pterodactyl\Exceptions\DisplayException * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function updateSettings(NodeFormRequest $request, Node $node) + public function updateSettings(NodeFormRequest $request, Node $node): RedirectResponse { $this->updateService->handle($node, $request->normalize(), $request->input('reset_secret') === 'on'); $this->alert->success(trans('admin/node.notices.node_updated'))->flash(); @@ -203,12 +122,8 @@ public function allocationRemoveMultiple(Request $request, int $node): Response /** * Remove all allocations for a specific IP at once on a node. - * - * @param int $node - * - * @return \Illuminate\Http\RedirectResponse */ - public function allocationRemoveBlock(Request $request, $node) + public function allocationRemoveBlock(Request $request, int $node): RedirectResponse { $this->allocationRepository->deleteWhere([ ['node_id', '=', $node], @@ -225,12 +140,10 @@ public function allocationRemoveBlock(Request $request, $node) /** * Sets an alias for a specific allocation on a node. * - * @return \Symfony\Component\HttpFoundation\Response - * * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function allocationSetAlias(AllocationAliasFormRequest $request) + public function allocationSetAlias(AllocationAliasFormRequest $request): \Symfony\Component\HttpFoundation\Response { $this->allocationRepository->update($request->input('allocation_id'), [ 'ip_alias' => (empty($request->input('alias'))) ? null : $request->input('alias'), @@ -242,16 +155,12 @@ public function allocationSetAlias(AllocationAliasFormRequest $request) /** * Creates new allocations on a node. * - * @param int|\Pterodactyl\Models\Node $node - * - * @return \Illuminate\Http\RedirectResponse - * * @throws \Pterodactyl\Exceptions\Service\Allocation\CidrOutOfRangeException * @throws \Pterodactyl\Exceptions\Service\Allocation\InvalidPortMappingException * @throws \Pterodactyl\Exceptions\Service\Allocation\PortOutOfRangeException * @throws \Pterodactyl\Exceptions\Service\Allocation\TooManyPortsInRangeException */ - public function createAllocation(AllocationFormRequest $request, Node $node) + public function createAllocation(AllocationFormRequest $request, Node $node): RedirectResponse { $this->assignmentService->handle($node, $request->normalize()); $this->alert->success(trans('admin/node.notices.allocations_added'))->flash(); @@ -262,13 +171,9 @@ public function createAllocation(AllocationFormRequest $request, Node $node) /** * Deletes a node from the system. * - * @param $node - * - * @return \Illuminate\Http\RedirectResponse - * * @throws \Pterodactyl\Exceptions\DisplayException */ - public function delete($node) + public function delete(int|Node $node): RedirectResponse { $this->deletionService->handle($node); $this->alert->success(trans('admin/node.notices.node_deleted'))->flash(); diff --git a/app/Http/Controllers/Admin/Servers/CreateServerController.php b/app/Http/Controllers/Admin/Servers/CreateServerController.php index b8f5a320b8..c7a1653ad4 100644 --- a/app/Http/Controllers/Admin/Servers/CreateServerController.php +++ b/app/Http/Controllers/Admin/Servers/CreateServerController.php @@ -3,77 +3,40 @@ namespace Pterodactyl\Http\Controllers\Admin\Servers; use JavaScript; +use Illuminate\View\View; +use Pterodactyl\Models\Node; +use Pterodactyl\Models\Location; use Illuminate\Http\RedirectResponse; use Prologue\Alerts\AlertsMessageBag; +use Illuminate\View\Factory as ViewFactory; use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Repositories\Eloquent\NestRepository; use Pterodactyl\Repositories\Eloquent\NodeRepository; use Pterodactyl\Http\Requests\Admin\ServerFormRequest; -use Pterodactyl\Repositories\Eloquent\ServerRepository; use Pterodactyl\Services\Servers\ServerCreationService; -use Pterodactyl\Repositories\Eloquent\LocationRepository; class CreateServerController extends Controller { - /** - * @var \Pterodactyl\Repositories\Eloquent\ServerRepository - */ - private $repository; - - /** - * @var \Pterodactyl\Repositories\Eloquent\NodeRepository - */ - private $nodeRepository; - - /** - * @var \Prologue\Alerts\AlertsMessageBag - */ - private $alert; - - /** - * @var \Pterodactyl\Repositories\Eloquent\NestRepository - */ - private $nestRepository; - - /** - * @var \Pterodactyl\Repositories\Eloquent\LocationRepository - */ - private $locationRepository; - - /** - * @var \Pterodactyl\Services\Servers\ServerCreationService - */ - private $creationService; - /** * CreateServerController constructor. */ public function __construct( - AlertsMessageBag $alert, - NestRepository $nestRepository, - LocationRepository $locationRepository, - NodeRepository $nodeRepository, - ServerRepository $repository, - ServerCreationService $creationService + private AlertsMessageBag $alert, + private NestRepository $nestRepository, + private NodeRepository $nodeRepository, + private ServerCreationService $creationService, + private ViewFactory $view ) { - $this->repository = $repository; - $this->nodeRepository = $nodeRepository; - $this->alert = $alert; - $this->nestRepository = $nestRepository; - $this->locationRepository = $locationRepository; - $this->creationService = $creationService; } /** * Displays the create server page. * - * @return \Illuminate\Contracts\View\Factory - * * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function index() + public function index(): View|RedirectResponse { - $nodes = $this->nodeRepository->all(); + $nodes = Node::all(); if (count($nodes) < 1) { $this->alert->warning(trans('admin/server.alerts.node_required'))->flash(); @@ -82,7 +45,7 @@ public function index() $nests = $this->nestRepository->getWithEggs(); - Javascript::put([ + JavaScript::put([ 'nodeData' => $this->nodeRepository->getNodesForServerCreation(), 'nests' => $nests->map(function ($item) { return array_merge($item->toArray(), [ @@ -91,8 +54,8 @@ public function index() })->keyBy('id'), ]); - return view('admin.servers.new', [ - 'locations' => $this->locationRepository->all(), + return $this->view->make('admin.servers.new', [ + 'locations' => Location::all(), 'nests' => $nests, ]); } @@ -100,15 +63,13 @@ public function index() /** * Create a new server on the remote system. * - * @return \Illuminate\Http\RedirectResponse - * * @throws \Illuminate\Validation\ValidationException * @throws \Pterodactyl\Exceptions\DisplayException * @throws \Pterodactyl\Exceptions\Service\Deployment\NoViableAllocationException * @throws \Pterodactyl\Exceptions\Service\Deployment\NoViableNodeException * @throws \Throwable */ - public function store(ServerFormRequest $request) + public function store(ServerFormRequest $request): RedirectResponse { $data = $request->except(['_token']); if (!empty($data['custom_image'])) { @@ -118,10 +79,8 @@ public function store(ServerFormRequest $request) $server = $this->creationService->handle($data); - $this->alert->success( - trans('admin/server.alerts.server_created') - )->flash(); + $this->alert->success(trans('admin/server.alerts.server_created'))->flash(); - return RedirectResponse::create('/admin/servers/view/' . $server->id); + return new RedirectResponse('/admin/servers/view/' . $server->id); } } diff --git a/app/Http/Controllers/Admin/Servers/ServerController.php b/app/Http/Controllers/Admin/Servers/ServerController.php index f369b7ad5e..430c3f2b91 100644 --- a/app/Http/Controllers/Admin/Servers/ServerController.php +++ b/app/Http/Controllers/Admin/Servers/ServerController.php @@ -2,45 +2,29 @@ namespace Pterodactyl\Http\Controllers\Admin\Servers; +use Illuminate\View\View; use Illuminate\Http\Request; use Pterodactyl\Models\Server; use Spatie\QueryBuilder\QueryBuilder; -use Illuminate\Contracts\View\Factory; use Spatie\QueryBuilder\AllowedFilter; use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Models\Filters\AdminServerFilter; -use Pterodactyl\Repositories\Eloquent\ServerRepository; +use Illuminate\Contracts\View\Factory as ViewFactory; class ServerController extends Controller { - /** - * @var \Illuminate\Contracts\View\Factory - */ - private $view; - - /** - * @var \Pterodactyl\Repositories\Eloquent\ServerRepository - */ - private $repository; - /** * ServerController constructor. */ - public function __construct( - Factory $view, - ServerRepository $repository - ) { - $this->view = $view; - $this->repository = $repository; + public function __construct(private ViewFactory $view) + { } /** - * Returns all of the servers that exist on the system using a paginated result set. If + * Returns all the servers that exist on the system using a paginated result set. If * a query is passed along in the request it is also passed to the repository function. - * - * @return \Illuminate\Contracts\View\View */ - public function index(Request $request) + public function index(Request $request): View { $servers = QueryBuilder::for(Server::query()->with('node', 'user', 'allocation')) ->allowedFilters([ diff --git a/app/Http/Controllers/Admin/Servers/ServerTransferController.php b/app/Http/Controllers/Admin/Servers/ServerTransferController.php index 9166ae3e92..cdb2bb5579 100644 --- a/app/Http/Controllers/Admin/Servers/ServerTransferController.php +++ b/app/Http/Controllers/Admin/Servers/ServerTransferController.php @@ -5,81 +5,34 @@ use Illuminate\Http\Request; use Pterodactyl\Models\Server; use Prologue\Alerts\AlertsMessageBag; +use Illuminate\Http\RedirectResponse; use Pterodactyl\Models\ServerTransfer; use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Services\Servers\TransferService; use Pterodactyl\Repositories\Eloquent\NodeRepository; -use Pterodactyl\Repositories\Eloquent\ServerRepository; -use Pterodactyl\Repositories\Eloquent\LocationRepository; use Pterodactyl\Repositories\Wings\DaemonConfigurationRepository; use Pterodactyl\Contracts\Repository\AllocationRepositoryInterface; class ServerTransferController extends Controller { - /** - * @var \Prologue\Alerts\AlertsMessageBag - */ - private $alert; - - /** - * @var \Pterodactyl\Contracts\Repository\AllocationRepositoryInterface - */ - private $allocationRepository; - - /** - * @var \Pterodactyl\Repositories\Eloquent\ServerRepository - */ - private $repository; - - /** - * @var \Pterodactyl\Repositories\Eloquent\LocationRepository - */ - private $locationRepository; - - /** - * @var \Pterodactyl\Repositories\Eloquent\NodeRepository - */ - private $nodeRepository; - - /** - * @var \Pterodactyl\Services\Servers\TransferService - */ - private $transferService; - - /** - * @var \Pterodactyl\Repositories\Wings\DaemonConfigurationRepository - */ - private $daemonConfigurationRepository; - /** * ServerTransferController constructor. */ public function __construct( - AlertsMessageBag $alert, - AllocationRepositoryInterface $allocationRepository, - ServerRepository $repository, - LocationRepository $locationRepository, - NodeRepository $nodeRepository, - TransferService $transferService, - DaemonConfigurationRepository $daemonConfigurationRepository + private AlertsMessageBag $alert, + private AllocationRepositoryInterface $allocationRepository, + private NodeRepository $nodeRepository, + private TransferService $transferService, + private DaemonConfigurationRepository $daemonConfigurationRepository ) { - $this->alert = $alert; - $this->allocationRepository = $allocationRepository; - $this->repository = $repository; - $this->locationRepository = $locationRepository; - $this->nodeRepository = $nodeRepository; - $this->transferService = $transferService; - $this->daemonConfigurationRepository = $daemonConfigurationRepository; } /** * Starts a transfer of a server to a new node. * - * @return \Illuminate\Http\RedirectResponse - * * @throws \Throwable */ - public function transfer(Request $request, Server $server) + public function transfer(Request $request, Server $server): RedirectResponse { $validatedData = $request->validate([ 'node_id' => 'required|exists:nodes,id', @@ -112,7 +65,7 @@ public function transfer(Request $request, Server $server) $transfer->save(); - // Add the allocations to the server so they cannot be automatically assigned while the transfer is in progress. + // Add the allocations to the server, so they cannot be automatically assigned while the transfer is in progress. $this->assignAllocationsToServer($server, $node_id, $allocation_id, $additional_allocations); // Request an archive from the server's current daemon. (this also checks if the daemon is online) @@ -132,7 +85,7 @@ public function transfer(Request $request, Server $server) private function assignAllocationsToServer(Server $server, int $node_id, int $allocation_id, array $additional_allocations) { $allocations = $additional_allocations; - array_push($allocations, $allocation_id); + $allocations[] = $allocation_id; $unassigned = $this->allocationRepository->getUnassignedAllocationIds($node_id); diff --git a/app/Http/Controllers/Admin/Servers/ServerViewController.php b/app/Http/Controllers/Admin/Servers/ServerViewController.php index 9186a8935f..7cf64a2f57 100644 --- a/app/Http/Controllers/Admin/Servers/ServerViewController.php +++ b/app/Http/Controllers/Admin/Servers/ServerViewController.php @@ -3,13 +3,14 @@ namespace Pterodactyl\Http\Controllers\Admin\Servers; use JavaScript; +use Illuminate\View\View; use Illuminate\Http\Request; use Pterodactyl\Models\Nest; use Pterodactyl\Models\Server; -use Illuminate\Contracts\View\Factory; use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Services\Servers\EnvironmentService; +use Illuminate\Contracts\View\Factory as ViewFactory; use Pterodactyl\Repositories\Eloquent\NestRepository; use Pterodactyl\Repositories\Eloquent\NodeRepository; use Pterodactyl\Repositories\Eloquent\MountRepository; @@ -22,95 +23,41 @@ class ServerViewController extends Controller { use JavascriptInjection; - /** - * @var \Illuminate\Contracts\View\Factory - */ - private $view; - - /** - * @var \Pterodactyl\Repositories\Eloquent\DatabaseHostRepository - */ - private $databaseHostRepository; - - /** - * @var \Pterodactyl\Repositories\Eloquent\ServerRepository - */ - private $repository; - - /** - * @var \Pterodactyl\Repositories\Eloquent\MountRepository - */ - protected $mountRepository; - - /** - * @var \Pterodactyl\Repositories\Eloquent\NestRepository - */ - private $nestRepository; - - /** - * @var \Pterodactyl\Repositories\Eloquent\LocationRepository - */ - private $locationRepository; - - /** - * @var \Pterodactyl\Repositories\Eloquent\NodeRepository - */ - private $nodeRepository; - - /** - * @var \Pterodactyl\Services\Servers\EnvironmentService - */ - private $environmentService; - /** * ServerViewController constructor. */ public function __construct( - Factory $view, - DatabaseHostRepository $databaseHostRepository, - LocationRepository $locationRepository, - MountRepository $mountRepository, - NestRepository $nestRepository, - NodeRepository $nodeRepository, - ServerRepository $repository, - EnvironmentService $environmentService + private DatabaseHostRepository $databaseHostRepository, + private LocationRepository $locationRepository, + private MountRepository $mountRepository, + private NestRepository $nestRepository, + private NodeRepository $nodeRepository, + private ServerRepository $repository, + private EnvironmentService $environmentService, + private ViewFactory $view ) { - $this->view = $view; - $this->databaseHostRepository = $databaseHostRepository; - $this->locationRepository = $locationRepository; - $this->mountRepository = $mountRepository; - $this->nestRepository = $nestRepository; - $this->nodeRepository = $nodeRepository; - $this->repository = $repository; - $this->environmentService = $environmentService; } /** * Returns the index view for a server. - * - * @return \Illuminate\Contracts\View\View */ - public function index(Request $request, Server $server) + public function index(Request $request, Server $server): View { return $this->view->make('admin.servers.view.index', compact('server')); } /** * Returns the server details page. - * - * @return \Illuminate\Contracts\View\View */ - public function details(Request $request, Server $server) + public function details(Request $request, Server $server): View { return $this->view->make('admin.servers.view.details', compact('server')); } /** * Returns a view of server build settings. - * - * @return \Illuminate\Contracts\View\View */ - public function build(Request $request, Server $server) + public function build(Request $request, Server $server): View { $allocations = $server->node->allocations->toBase(); @@ -124,11 +71,9 @@ public function build(Request $request, Server $server) /** * Returns the server startup management page. * - * @return \Illuminate\Contracts\View\View - * * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function startup(Request $request, Server $server) + public function startup(Request $request, Server $server): View { $nests = $this->nestRepository->getWithEggs(); $variables = $this->environmentService->handle($server); @@ -147,11 +92,9 @@ public function startup(Request $request, Server $server) } /** - * Returns all of the databases that exist for the server. - * - * @return \Illuminate\Contracts\View\View + * Returns all the databases that exist for the server. */ - public function database(Request $request, Server $server) + public function database(Request $request, Server $server): View { return $this->view->make('admin.servers.view.database', [ 'hosts' => $this->databaseHostRepository->all(), @@ -160,11 +103,9 @@ public function database(Request $request, Server $server) } /** - * Returns all of the mounts that exist for the server. - * - * @return \Illuminate\Contracts\View\View + * Returns all the mounts that exist for the server. */ - public function mounts(Request $request, Server $server) + public function mounts(Request $request, Server $server): View { $server->load('mounts'); @@ -178,11 +119,9 @@ public function mounts(Request $request, Server $server) * Returns the base server management page, or an exception if the server * is in a state that cannot be recovered from. * - * @return \Illuminate\Contracts\View\View - * * @throws \Pterodactyl\Exceptions\DisplayException */ - public function manage(Request $request, Server $server) + public function manage(Request $request, Server $server): View { if ($server->status === Server::STATUS_INSTALL_FAILED) { throw new DisplayException('This server is in a failed install state and cannot be recovered. Please delete and re-create the server.'); @@ -195,7 +134,7 @@ public function manage(Request $request, Server $server) $canTransfer = true; } - Javascript::put([ + JavaScript::put([ 'nodeData' => $this->nodeRepository->getNodesForServerCreation(), ]); @@ -208,10 +147,8 @@ public function manage(Request $request, Server $server) /** * Returns the server deletion page. - * - * @return \Illuminate\Contracts\View\View */ - public function delete(Request $request, Server $server) + public function delete(Request $request, Server $server): View { return $this->view->make('admin.servers.view.delete', compact('server')); } diff --git a/app/Http/Controllers/Admin/ServersController.php b/app/Http/Controllers/Admin/ServersController.php index c226638b92..3d2c55ac40 100644 --- a/app/Http/Controllers/Admin/ServersController.php +++ b/app/Http/Controllers/Admin/ServersController.php @@ -1,20 +1,15 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Http\Controllers\Admin; use Illuminate\Http\Request; use Pterodactyl\Models\User; +use Illuminate\Http\Response; use Pterodactyl\Models\Mount; use Pterodactyl\Models\Server; use Pterodactyl\Models\Database; use Pterodactyl\Models\MountServer; +use Illuminate\Http\RedirectResponse; use Prologue\Alerts\AlertsMessageBag; use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Http\Controllers\Controller; @@ -41,148 +36,38 @@ class ServersController extends Controller { - /** - * @var \Prologue\Alerts\AlertsMessageBag - */ - protected $alert; - - /** - * @var \Pterodactyl\Contracts\Repository\AllocationRepositoryInterface - */ - protected $allocationRepository; - - /** - * @var \Pterodactyl\Services\Servers\BuildModificationService - */ - protected $buildModificationService; - - /** - * @var \Illuminate\Contracts\Config\Repository - */ - protected $config; - - /** - * @var \Pterodactyl\Repositories\Wings\DaemonServerRepository - */ - private $daemonServerRepository; - - /** - * @var \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface - */ - protected $databaseRepository; - - /** - * @var \Pterodactyl\Services\Databases\DatabaseManagementService - */ - protected $databaseManagementService; - - /** - * @var \Pterodactyl\Services\Databases\DatabasePasswordService - */ - protected $databasePasswordService; - - /** - * @var \Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface - */ - protected $databaseHostRepository; - - /** - * @var \Pterodactyl\Services\Servers\ServerDeletionService - */ - protected $deletionService; - - /** - * @var \Pterodactyl\Services\Servers\DetailsModificationService - */ - protected $detailsModificationService; - - /** - * @var \Pterodactyl\Repositories\Eloquent\MountRepository - */ - protected $mountRepository; - - /** - * @var \Pterodactyl\Contracts\Repository\NestRepositoryInterface - */ - protected $nestRepository; - - /** - * @var \Pterodactyl\Services\Servers\ReinstallServerService - */ - protected $reinstallService; - - /** - * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface - */ - protected $repository; - - /** - * @var \Pterodactyl\Services\Servers\ServerConfigurationStructureService - */ - private $serverConfigurationStructureService; - - /** - * @var \Pterodactyl\Services\Servers\StartupModificationService - */ - private $startupModificationService; - - /** - * @var \Pterodactyl\Services\Servers\SuspensionService - */ - protected $suspensionService; - /** * ServersController constructor. */ public function __construct( - AlertsMessageBag $alert, - AllocationRepositoryInterface $allocationRepository, - BuildModificationService $buildModificationService, - ConfigRepository $config, - DaemonServerRepository $daemonServerRepository, - DatabaseManagementService $databaseManagementService, - DatabasePasswordService $databasePasswordService, - DatabaseRepositoryInterface $databaseRepository, - DatabaseHostRepository $databaseHostRepository, - ServerDeletionService $deletionService, - DetailsModificationService $detailsModificationService, - ReinstallServerService $reinstallService, - ServerRepositoryInterface $repository, - MountRepository $mountRepository, - NestRepositoryInterface $nestRepository, - ServerConfigurationStructureService $serverConfigurationStructureService, - StartupModificationService $startupModificationService, - SuspensionService $suspensionService + protected AlertsMessageBag $alert, + protected AllocationRepositoryInterface $allocationRepository, + protected BuildModificationService $buildModificationService, + protected ConfigRepository $config, + protected DaemonServerRepository $daemonServerRepository, + protected DatabaseManagementService $databaseManagementService, + protected DatabasePasswordService $databasePasswordService, + protected DatabaseRepositoryInterface $databaseRepository, + protected DatabaseHostRepository $databaseHostRepository, + protected ServerDeletionService $deletionService, + protected DetailsModificationService $detailsModificationService, + protected ReinstallServerService $reinstallService, + protected ServerRepositoryInterface $repository, + protected MountRepository $mountRepository, + protected NestRepositoryInterface $nestRepository, + protected ServerConfigurationStructureService $serverConfigurationStructureService, + protected StartupModificationService $startupModificationService, + protected SuspensionService $suspensionService ) { - $this->alert = $alert; - $this->allocationRepository = $allocationRepository; - $this->buildModificationService = $buildModificationService; - $this->config = $config; - $this->daemonServerRepository = $daemonServerRepository; - $this->databaseHostRepository = $databaseHostRepository; - $this->databaseManagementService = $databaseManagementService; - $this->databasePasswordService = $databasePasswordService; - $this->databaseRepository = $databaseRepository; - $this->detailsModificationService = $detailsModificationService; - $this->deletionService = $deletionService; - $this->nestRepository = $nestRepository; - $this->reinstallService = $reinstallService; - $this->repository = $repository; - $this->mountRepository = $mountRepository; - $this->serverConfigurationStructureService = $serverConfigurationStructureService; - $this->startupModificationService = $startupModificationService; - $this->suspensionService = $suspensionService; } /** * Update the details for a server. * - * @return \Illuminate\Http\RedirectResponse - * * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function setDetails(Request $request, Server $server) + public function setDetails(Request $request, Server $server): RedirectResponse { $this->detailsModificationService->handle($server, $request->only([ 'owner_id', 'external_id', 'name', 'description', @@ -194,15 +79,13 @@ public function setDetails(Request $request, Server $server) } /** - * Toggles the install status for a server. - * - * @return \Illuminate\Http\RedirectResponse + * Toggles the installation status for a server. * * @throws \Pterodactyl\Exceptions\DisplayException * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function toggleInstall(Server $server) + public function toggleInstall(Server $server): RedirectResponse { if ($server->status === Server::STATUS_INSTALL_FAILED) { throw new DisplayException(trans('admin/server.exceptions.marked_as_failed')); @@ -220,13 +103,11 @@ public function toggleInstall(Server $server) /** * Reinstalls the server with the currently assigned service. * - * @return \Illuminate\Http\RedirectResponse - * * @throws \Pterodactyl\Exceptions\DisplayException * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function reinstallServer(Server $server) + public function reinstallServer(Server $server): RedirectResponse { $this->reinstallService->handle($server); $this->alert->success(trans('admin/server.alerts.server_reinstalled'))->flash(); @@ -237,13 +118,11 @@ public function reinstallServer(Server $server) /** * Manage the suspension status for a server. * - * @return \Illuminate\Http\RedirectResponse - * * @throws \Pterodactyl\Exceptions\DisplayException * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function manageSuspension(Request $request, Server $server) + public function manageSuspension(Request $request, Server $server): RedirectResponse { $this->suspensionService->toggle($server, $request->input('action')); $this->alert->success(trans('admin/server.alerts.suspension_toggled', [ @@ -256,13 +135,11 @@ public function manageSuspension(Request $request, Server $server) /** * Update the build configuration for a server. * - * @return \Illuminate\Http\RedirectResponse - * * @throws \Pterodactyl\Exceptions\DisplayException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException * @throws \Illuminate\Validation\ValidationException */ - public function updateBuild(Request $request, Server $server) + public function updateBuild(Request $request, Server $server): RedirectResponse { try { $this->buildModificationService->handle($server, $request->only([ @@ -282,12 +159,10 @@ public function updateBuild(Request $request, Server $server) /** * Start the server deletion process. * - * @return \Illuminate\Http\RedirectResponse - * * @throws \Pterodactyl\Exceptions\DisplayException * @throws \Throwable */ - public function delete(Request $request, Server $server) + public function delete(Request $request, Server $server): RedirectResponse { $this->deletionService->withForce($request->filled('force_delete'))->handle($server); $this->alert->success(trans('admin/server.alerts.server_deleted'))->flash(); @@ -298,11 +173,9 @@ public function delete(Request $request, Server $server) /** * Update the startup command as well as variables. * - * @return \Illuminate\Http\RedirectResponse - * * @throws \Illuminate\Validation\ValidationException */ - public function saveStartup(Request $request, Server $server) + public function saveStartup(Request $request, Server $server): RedirectResponse { $data = $request->except('_token'); if (!empty($data['custom_docker_image'])) { @@ -326,11 +199,9 @@ public function saveStartup(Request $request, Server $server) /** * Creates a new database assigned to a specific server. * - * @return \Illuminate\Http\RedirectResponse - * * @throws \Throwable */ - public function newDatabase(StoreServerDatabaseRequest $request, Server $server) + public function newDatabase(StoreServerDatabaseRequest $request, Server $server): RedirectResponse { $this->databaseManagementService->create($server, [ 'database' => DatabaseManagementService::generateUniqueDatabaseName($request->input('database'), $server->id), @@ -345,13 +216,12 @@ public function newDatabase(StoreServerDatabaseRequest $request, Server $server) /** * Resets the database password for a specific database on this server. * - * @return \Illuminate\Http\Response - * * @throws \Throwable */ - public function resetDatabasePassword(Request $request, Server $server) + public function resetDatabasePassword(Request $request, Server $server): Response { - $database = $server->databases()->where('id', $request->input('database'))->findOrFail(); + /** @var \Pterodactyl\Models\Database $database */ + $database = $server->databases()->findOrFail($request->input('database')); $this->databasePasswordService->handle($database); @@ -361,11 +231,9 @@ public function resetDatabasePassword(Request $request, Server $server) /** * Deletes a database from a server. * - * @return \Illuminate\Http\Response - * * @throws \Exception */ - public function deleteDatabase(Server $server, Database $database) + public function deleteDatabase(Server $server, Database $database): Response { $this->databaseManagementService->delete($database); @@ -375,11 +243,9 @@ public function deleteDatabase(Server $server, Database $database) /** * Add a mount to a server. * - * @return \Illuminate\Http\RedirectResponse - * * @throws \Throwable */ - public function addMount(Request $request, Server $server) + public function addMount(Request $request, Server $server): RedirectResponse { $mountServer = (new MountServer())->forceFill([ 'mount_id' => $request->input('mount_id'), @@ -395,10 +261,8 @@ public function addMount(Request $request, Server $server) /** * Remove a mount from a server. - * - * @return \Illuminate\Http\RedirectResponse */ - public function deleteMount(Server $server, Mount $mount) + public function deleteMount(Server $server, Mount $mount): RedirectResponse { MountServer::where('mount_id', $mount->id)->where('server_id', $server->id)->delete(); diff --git a/app/Http/Controllers/Admin/Settings/AdvancedController.php b/app/Http/Controllers/Admin/Settings/AdvancedController.php index 49959bddd9..bf68832325 100644 --- a/app/Http/Controllers/Admin/Settings/AdvancedController.php +++ b/app/Http/Controllers/Admin/Settings/AdvancedController.php @@ -6,6 +6,7 @@ use Illuminate\Http\RedirectResponse; use Prologue\Alerts\AlertsMessageBag; use Illuminate\Contracts\Console\Kernel; +use Illuminate\View\Factory as ViewFactory; use Pterodactyl\Http\Controllers\Controller; use Illuminate\Contracts\Config\Repository as ConfigRepository; use Pterodactyl\Contracts\Repository\SettingsRepositoryInterface; @@ -13,39 +14,16 @@ class AdvancedController extends Controller { - /** - * @var \Prologue\Alerts\AlertsMessageBag - */ - private $alert; - - /** - * @var \Illuminate\Contracts\Config\Repository - */ - private $config; - - /** - * @var \Illuminate\Contracts\Console\Kernel - */ - private $kernel; - - /** - * @var \Pterodactyl\Contracts\Repository\SettingsRepositoryInterface - */ - private $settings; - /** * AdvancedController constructor. */ public function __construct( - AlertsMessageBag $alert, - ConfigRepository $config, - Kernel $kernel, - SettingsRepositoryInterface $settings + private AlertsMessageBag $alert, + private ConfigRepository $config, + private Kernel $kernel, + private SettingsRepositoryInterface $settings, + private ViewFactory $view ) { - $this->alert = $alert; - $this->config = $config; - $this->kernel = $kernel; - $this->settings = $settings; } /** @@ -61,7 +39,7 @@ public function index(): View $showRecaptchaWarning = true; } - return view('admin.settings.advanced', [ + return $this->view->make('admin.settings.advanced', [ 'showRecaptchaWarning' => $showRecaptchaWarning, ]); } diff --git a/app/Http/Controllers/Admin/Settings/IndexController.php b/app/Http/Controllers/Admin/Settings/IndexController.php index 190646fd42..eabede932c 100644 --- a/app/Http/Controllers/Admin/Settings/IndexController.php +++ b/app/Http/Controllers/Admin/Settings/IndexController.php @@ -6,6 +6,7 @@ use Illuminate\Http\RedirectResponse; use Prologue\Alerts\AlertsMessageBag; use Illuminate\Contracts\Console\Kernel; +use Illuminate\View\Factory as ViewFactory; use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Traits\Helpers\AvailableLanguages; use Pterodactyl\Services\Helpers\SoftwareVersionService; @@ -16,39 +17,16 @@ class IndexController extends Controller { use AvailableLanguages; - /** - * @var \Prologue\Alerts\AlertsMessageBag - */ - private $alert; - - /** - * @var \Illuminate\Contracts\Console\Kernel - */ - private $kernel; - - /** - * @var \Pterodactyl\Contracts\Repository\SettingsRepositoryInterface - */ - private $settings; - - /** - * @var \Pterodactyl\Services\Helpers\SoftwareVersionService - */ - private $versionService; - /** * IndexController constructor. */ public function __construct( - AlertsMessageBag $alert, - Kernel $kernel, - SettingsRepositoryInterface $settings, - SoftwareVersionService $versionService + private AlertsMessageBag $alert, + private Kernel $kernel, + private SettingsRepositoryInterface $settings, + private SoftwareVersionService $versionService, + private ViewFactory $view ) { - $this->alert = $alert; - $this->kernel = $kernel; - $this->settings = $settings; - $this->versionService = $versionService; } /** @@ -56,7 +34,7 @@ public function __construct( */ public function index(): View { - return view('admin.settings.index', [ + return $this->view->make('admin.settings.index', [ 'version' => $this->versionService, 'languages' => $this->getAvailableLanguages(true), ]); diff --git a/app/Http/Controllers/Admin/Settings/MailController.php b/app/Http/Controllers/Admin/Settings/MailController.php index 8f3e15ed6c..058e3a90e6 100644 --- a/app/Http/Controllers/Admin/Settings/MailController.php +++ b/app/Http/Controllers/Admin/Settings/MailController.php @@ -6,9 +6,9 @@ use Illuminate\View\View; use Illuminate\Http\Request; use Illuminate\Http\Response; -use Prologue\Alerts\AlertsMessageBag; use Illuminate\Contracts\Console\Kernel; use Pterodactyl\Notifications\MailTested; +use Illuminate\View\Factory as ViewFactory; use Illuminate\Support\Facades\Notification; use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Http\Controllers\Controller; @@ -20,46 +20,16 @@ class MailController extends Controller { - /** - * @var \Prologue\Alerts\AlertsMessageBag - */ - private $alert; - - /** - * @var \Illuminate\Contracts\Config\Repository - */ - private $config; - - /** - * @var \Illuminate\Contracts\Encryption\Encrypter - */ - private $encrypter; - - /** - * @var \Illuminate\Contracts\Console\Kernel - */ - private $kernel; - - /** - * @var \Pterodactyl\Contracts\Repository\SettingsRepositoryInterface - */ - private $settings; - /** * MailController constructor. */ public function __construct( - AlertsMessageBag $alert, - ConfigRepository $config, - Encrypter $encrypter, - Kernel $kernel, - SettingsRepositoryInterface $settings + private ConfigRepository $config, + private Encrypter $encrypter, + private Kernel $kernel, + private SettingsRepositoryInterface $settings, + private ViewFactory $view ) { - $this->alert = $alert; - $this->config = $config; - $this->encrypter = $encrypter; - $this->kernel = $kernel; - $this->settings = $settings; } /** @@ -68,7 +38,7 @@ public function __construct( */ public function index(): View { - return view('admin.settings.mail', [ + return $this->view->make('admin.settings.mail', [ 'disabled' => $this->config->get('mail.driver') !== 'smtp', ]); } diff --git a/app/Http/Controllers/Admin/UserController.php b/app/Http/Controllers/Admin/UserController.php index b13ee62bfa..254a72f8c9 100644 --- a/app/Http/Controllers/Admin/UserController.php +++ b/app/Http/Controllers/Admin/UserController.php @@ -2,10 +2,15 @@ namespace Pterodactyl\Http\Controllers\Admin; +use Illuminate\View\View; use Illuminate\Http\Request; use Pterodactyl\Models\User; +use Pterodactyl\Models\Model; +use Illuminate\Support\Collection; +use Illuminate\Http\RedirectResponse; use Prologue\Alerts\AlertsMessageBag; use Spatie\QueryBuilder\QueryBuilder; +use Illuminate\View\Factory as ViewFactory; use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Http\Controllers\Controller; use Illuminate\Contracts\Translation\Translator; @@ -20,61 +25,24 @@ class UserController extends Controller { use AvailableLanguages; - /** - * @var \Prologue\Alerts\AlertsMessageBag - */ - protected $alert; - - /** - * @var \Pterodactyl\Services\Users\UserCreationService - */ - protected $creationService; - - /** - * @var \Pterodactyl\Services\Users\UserDeletionService - */ - protected $deletionService; - - /** - * @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface - */ - protected $repository; - - /** - * @var \Illuminate\Contracts\Translation\Translator - */ - protected $translator; - - /** - * @var \Pterodactyl\Services\Users\UserUpdateService - */ - protected $updateService; - /** * UserController constructor. */ public function __construct( - AlertsMessageBag $alert, - UserCreationService $creationService, - UserDeletionService $deletionService, - Translator $translator, - UserUpdateService $updateService, - UserRepositoryInterface $repository + protected AlertsMessageBag $alert, + protected UserCreationService $creationService, + protected UserDeletionService $deletionService, + protected Translator $translator, + protected UserUpdateService $updateService, + protected UserRepositoryInterface $repository, + protected ViewFactory $view ) { - $this->alert = $alert; - $this->creationService = $creationService; - $this->deletionService = $deletionService; - $this->repository = $repository; - $this->translator = $translator; - $this->updateService = $updateService; } /** * Display user index page. - * - * @return \Illuminate\View\View */ - public function index(Request $request) + public function index(Request $request): View { $users = QueryBuilder::for( User::query()->select('users.*') @@ -88,29 +56,25 @@ public function index(Request $request) ->allowedSorts(['id', 'uuid']) ->paginate(50); - return view('admin.users.index', ['users' => $users]); + return $this->view->make('admin.users.index', ['users' => $users]); } /** * Display new user page. - * - * @return \Illuminate\View\View */ - public function create() + public function create(): View { - return view('admin.users.new', [ + return $this->view->make('admin.users.new', [ 'languages' => $this->getAvailableLanguages(true), ]); } /** * Display user view page. - * - * @return \Illuminate\View\View */ - public function view(User $user) + public function view(User $user): View { - return view('admin.users.view', [ + return $this->view->make('admin.users.view', [ 'user' => $user, 'languages' => $this->getAvailableLanguages(true), ]); @@ -119,12 +83,10 @@ public function view(User $user) /** * Delete a user from the system. * - * @return \Illuminate\Http\RedirectResponse - * * @throws \Exception * @throws \Pterodactyl\Exceptions\DisplayException */ - public function delete(Request $request, User $user) + public function delete(Request $request, User $user): RedirectResponse { if ($request->user()->id === $user->id) { throw new DisplayException($this->translator->get('admin/user.exceptions.user_has_servers')); @@ -138,12 +100,10 @@ public function delete(Request $request, User $user) /** * Create a user. * - * @return \Illuminate\Http\RedirectResponse - * * @throws \Exception * @throws \Throwable */ - public function store(UserFormRequest $request) + public function store(UserFormRequest $request): RedirectResponse { $user = $this->creationService->handle($request->normalize()); $this->alert->success($this->translator->get('admin/user.notices.account_created'))->flash(); @@ -154,12 +114,10 @@ public function store(UserFormRequest $request) /** * Update a user on the system. * - * @return \Illuminate\Http\RedirectResponse - * * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function update(UserFormRequest $request, User $user) + public function update(UserFormRequest $request, User $user): RedirectResponse { $this->updateService ->setUserLevel(User::USER_LEVEL_ADMIN) @@ -172,10 +130,8 @@ public function update(UserFormRequest $request, User $user) /** * Get a JSON response of users on the system. - * - * @return \Illuminate\Support\Collection|\Pterodactyl\Models\Model */ - public function json(Request $request) + public function json(Request $request): Model|Collection { $users = QueryBuilder::for(User::query())->allowedFilters(['email'])->paginate(25); diff --git a/app/Http/Controllers/Api/Application/ApplicationApiController.php b/app/Http/Controllers/Api/Application/ApplicationApiController.php index 91da818937..dfc2cad97d 100644 --- a/app/Http/Controllers/Api/Application/ApplicationApiController.php +++ b/app/Http/Controllers/Api/Application/ApplicationApiController.php @@ -13,15 +13,9 @@ abstract class ApplicationApiController extends Controller { - /** - * @var \Illuminate\Http\Request - */ - protected $request; + protected Request $request; - /** - * @var \Pterodactyl\Extensions\Spatie\Fractalistic\Fractal - */ - protected $fractal; + protected Fractal $fractal; /** * ApplicationApiController constructor. @@ -30,7 +24,7 @@ public function __construct() { Container::getInstance()->call([$this, 'loadDependencies']); - // Parse all of the includes to use on this request. + // Parse all the includes to use on this request. $input = $this->request->input('include', []); $input = is_array($input) ? $input : explode(',', $input); @@ -62,7 +56,6 @@ public function loadDependencies(Fractal $fractal, Request $request) * @return T * * @noinspection PhpDocSignatureInspection - * @noinspection PhpUndefinedClassInspection */ public function getTransformer(string $abstract) { @@ -72,7 +65,7 @@ public function getTransformer(string $abstract) } /** - * Return a HTTP/204 response for the API. + * Return an HTTP/204 response for the API. */ protected function returnNoContent(): Response { diff --git a/app/Http/Controllers/Api/Application/Locations/LocationController.php b/app/Http/Controllers/Api/Application/Locations/LocationController.php index e8f166aba2..b95a0776a9 100644 --- a/app/Http/Controllers/Api/Application/Locations/LocationController.php +++ b/app/Http/Controllers/Api/Application/Locations/LocationController.php @@ -9,7 +9,6 @@ use Pterodactyl\Services\Locations\LocationUpdateService; use Pterodactyl\Services\Locations\LocationCreationService; use Pterodactyl\Services\Locations\LocationDeletionService; -use Pterodactyl\Contracts\Repository\LocationRepositoryInterface; use Pterodactyl\Transformers\Api\Application\LocationTransformer; use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController; use Pterodactyl\Http\Requests\Api\Application\Locations\GetLocationRequest; @@ -20,45 +19,19 @@ class LocationController extends ApplicationApiController { - /** - * @var \Pterodactyl\Services\Locations\LocationCreationService - */ - private $creationService; - - /** - * @var \Pterodactyl\Services\Locations\LocationDeletionService - */ - private $deletionService; - - /** - * @var \Pterodactyl\Contracts\Repository\LocationRepositoryInterface - */ - private $repository; - - /** - * @var \Pterodactyl\Services\Locations\LocationUpdateService - */ - private $updateService; - /** * LocationController constructor. */ public function __construct( - LocationCreationService $creationService, - LocationDeletionService $deletionService, - LocationRepositoryInterface $repository, - LocationUpdateService $updateService + private LocationCreationService $creationService, + private LocationDeletionService $deletionService, + private LocationUpdateService $updateService ) { parent::__construct(); - - $this->creationService = $creationService; - $this->deletionService = $deletionService; - $this->repository = $repository; - $this->updateService = $updateService; } /** - * Return all of the locations currently registered on the Panel. + * Return all the locations currently registered on the Panel. */ public function index(GetLocationsRequest $request): array { @@ -83,7 +56,7 @@ public function view(GetLocationRequest $request, Location $location): array } /** - * Store a new location on the Panel and return a HTTP/201 response code with the + * Store a new location on the Panel and return an HTTP/201 response code with the * new location attached. * * @throws \Pterodactyl\Exceptions\Model\DataValidationException diff --git a/app/Http/Controllers/Api/Application/Nests/NestController.php b/app/Http/Controllers/Api/Application/Nests/NestController.php index 232bf026c1..f0044f53c9 100644 --- a/app/Http/Controllers/Api/Application/Nests/NestController.php +++ b/app/Http/Controllers/Api/Application/Nests/NestController.php @@ -10,19 +10,12 @@ class NestController extends ApplicationApiController { - /** - * @var \Pterodactyl\Contracts\Repository\NestRepositoryInterface - */ - private $repository; - /** * NestController constructor. */ - public function __construct(NestRepositoryInterface $repository) + public function __construct(private NestRepositoryInterface $repository) { parent::__construct(); - - $this->repository = $repository; } /** diff --git a/app/Http/Controllers/Api/Application/Nodes/AllocationController.php b/app/Http/Controllers/Api/Application/Nodes/AllocationController.php index ecc7e1cd4f..77404945a4 100644 --- a/app/Http/Controllers/Api/Application/Nodes/AllocationController.php +++ b/app/Http/Controllers/Api/Application/Nodes/AllocationController.php @@ -18,31 +18,18 @@ class AllocationController extends ApplicationApiController { - /** - * @var \Pterodactyl\Services\Allocations\AssignmentService - */ - private $assignmentService; - - /** - * @var \Pterodactyl\Services\Allocations\AllocationDeletionService - */ - private $deletionService; - /** * AllocationController constructor. */ public function __construct( - AssignmentService $assignmentService, - AllocationDeletionService $deletionService + private AssignmentService $assignmentService, + private AllocationDeletionService $deletionService ) { parent::__construct(); - - $this->assignmentService = $assignmentService; - $this->deletionService = $deletionService; } /** - * Return all of the allocations that exist for a given node. + * Return all the allocations that exist for a given node. */ public function index(GetAllocationsRequest $request, Node $node): array { diff --git a/app/Http/Controllers/Api/Application/Nodes/NodeConfigurationController.php b/app/Http/Controllers/Api/Application/Nodes/NodeConfigurationController.php index 95ae70f3b9..2d812a89bb 100644 --- a/app/Http/Controllers/Api/Application/Nodes/NodeConfigurationController.php +++ b/app/Http/Controllers/Api/Application/Nodes/NodeConfigurationController.php @@ -13,10 +13,8 @@ class NodeConfigurationController extends ApplicationApiController * Returns the configuration information for a node. This allows for automated deployments * to remote machines so long as an API key is provided to the machine to make the request * with, and the node is known. - * - * @return \Illuminate\Http\JsonResponse */ - public function __invoke(GetNodeRequest $request, Node $node) + public function __invoke(GetNodeRequest $request, Node $node): JsonResponse { return new JsonResponse($node->getConfiguration()); } diff --git a/app/Http/Controllers/Api/Application/Nodes/NodeController.php b/app/Http/Controllers/Api/Application/Nodes/NodeController.php index 2daf187c2f..e0e3575d81 100644 --- a/app/Http/Controllers/Api/Application/Nodes/NodeController.php +++ b/app/Http/Controllers/Api/Application/Nodes/NodeController.php @@ -8,7 +8,6 @@ use Pterodactyl\Services\Nodes\NodeUpdateService; use Pterodactyl\Services\Nodes\NodeCreationService; use Pterodactyl\Services\Nodes\NodeDeletionService; -use Pterodactyl\Contracts\Repository\NodeRepositoryInterface; use Pterodactyl\Transformers\Api\Application\NodeTransformer; use Pterodactyl\Http\Requests\Api\Application\Nodes\GetNodeRequest; use Pterodactyl\Http\Requests\Api\Application\Nodes\GetNodesRequest; @@ -19,45 +18,19 @@ class NodeController extends ApplicationApiController { - /** - * @var \Pterodactyl\Services\Nodes\NodeCreationService - */ - private $creationService; - - /** - * @var \Pterodactyl\Services\Nodes\NodeDeletionService - */ - private $deletionService; - - /** - * @var \Pterodactyl\Contracts\Repository\NodeRepositoryInterface - */ - private $repository; - - /** - * @var \Pterodactyl\Services\Nodes\NodeUpdateService - */ - private $updateService; - /** * NodeController constructor. */ public function __construct( - NodeCreationService $creationService, - NodeDeletionService $deletionService, - NodeUpdateService $updateService, - NodeRepositoryInterface $repository + private NodeCreationService $creationService, + private NodeDeletionService $deletionService, + private NodeUpdateService $updateService ) { parent::__construct(); - - $this->repository = $repository; - $this->creationService = $creationService; - $this->deletionService = $deletionService; - $this->updateService = $updateService; } /** - * Return all of the nodes currently available on the Panel. + * Return all the nodes currently available on the Panel. */ public function index(GetNodesRequest $request): array { @@ -82,7 +55,7 @@ public function view(GetNodeRequest $request, Node $node): array } /** - * Create a new node on the Panel. Returns the created node and a HTTP/201 + * Create a new node on the Panel. Returns the created node and an HTTP/201 * status response on success. * * @throws \Pterodactyl\Exceptions\Model\DataValidationException diff --git a/app/Http/Controllers/Api/Application/Nodes/NodeDeploymentController.php b/app/Http/Controllers/Api/Application/Nodes/NodeDeploymentController.php index 9d5c75a058..4b49fa9c6a 100644 --- a/app/Http/Controllers/Api/Application/Nodes/NodeDeploymentController.php +++ b/app/Http/Controllers/Api/Application/Nodes/NodeDeploymentController.php @@ -9,19 +9,12 @@ class NodeDeploymentController extends ApplicationApiController { - /** - * @var \Pterodactyl\Services\Deployment\FindViableNodesService - */ - private $viableNodesService; - /** * NodeDeploymentController constructor. */ - public function __construct(FindViableNodesService $viableNodesService) + public function __construct(private FindViableNodesService $viableNodesService) { parent::__construct(); - - $this->viableNodesService = $viableNodesService; } /** diff --git a/app/Http/Controllers/Api/Application/Servers/DatabaseController.php b/app/Http/Controllers/Api/Application/Servers/DatabaseController.php index 2b9564728f..1717c5dd88 100644 --- a/app/Http/Controllers/Api/Application/Servers/DatabaseController.php +++ b/app/Http/Controllers/Api/Application/Servers/DatabaseController.php @@ -8,7 +8,6 @@ use Illuminate\Http\JsonResponse; use Pterodactyl\Services\Databases\DatabasePasswordService; use Pterodactyl\Services\Databases\DatabaseManagementService; -use Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface; use Pterodactyl\Transformers\Api\Application\ServerDatabaseTransformer; use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController; use Pterodactyl\Http\Requests\Api\Application\Servers\Databases\GetServerDatabaseRequest; @@ -18,34 +17,14 @@ class DatabaseController extends ApplicationApiController { - /** - * @var \Pterodactyl\Services\Databases\DatabaseManagementService - */ - private $databaseManagementService; - - /** - * @var \Pterodactyl\Services\Databases\DatabasePasswordService - */ - private $databasePasswordService; - - /** - * @var \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface - */ - private $repository; - /** * DatabaseController constructor. */ public function __construct( - DatabaseManagementService $databaseManagementService, - DatabasePasswordService $databasePasswordService, - DatabaseRepositoryInterface $repository + private DatabaseManagementService $databaseManagementService, + private DatabasePasswordService $databasePasswordService ) { parent::__construct(); - - $this->databaseManagementService = $databaseManagementService; - $this->databasePasswordService = $databasePasswordService; - $this->repository = $repository; } /** diff --git a/app/Http/Controllers/Api/Application/Servers/ServerController.php b/app/Http/Controllers/Api/Application/Servers/ServerController.php index 0072822e31..2eb69162dd 100644 --- a/app/Http/Controllers/Api/Application/Servers/ServerController.php +++ b/app/Http/Controllers/Api/Application/Servers/ServerController.php @@ -8,7 +8,6 @@ use Spatie\QueryBuilder\QueryBuilder; use Pterodactyl\Services\Servers\ServerCreationService; use Pterodactyl\Services\Servers\ServerDeletionService; -use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; use Pterodactyl\Transformers\Api\Application\ServerTransformer; use Pterodactyl\Http\Requests\Api\Application\Servers\GetServerRequest; use Pterodactyl\Http\Requests\Api\Application\Servers\GetServersRequest; @@ -18,38 +17,18 @@ class ServerController extends ApplicationApiController { - /** - * @var \Pterodactyl\Services\Servers\ServerCreationService - */ - private $creationService; - - /** - * @var \Pterodactyl\Services\Servers\ServerDeletionService - */ - private $deletionService; - - /** - * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface - */ - private $repository; - /** * ServerController constructor. */ public function __construct( - ServerCreationService $creationService, - ServerDeletionService $deletionService, - ServerRepositoryInterface $repository + private ServerCreationService $creationService, + private ServerDeletionService $deletionService ) { parent::__construct(); - - $this->creationService = $creationService; - $this->deletionService = $deletionService; - $this->repository = $repository; } /** - * Return all of the servers that currently exist on the Panel. + * Return all the servers that currently exist on the Panel. */ public function index(GetServersRequest $request): array { @@ -94,6 +73,8 @@ public function view(GetServerRequest $request, Server $server): array } /** + * Deletes a server. + * * @throws \Pterodactyl\Exceptions\DisplayException */ public function delete(ServerWriteRequest $request, Server $server, string $force = ''): Response diff --git a/app/Http/Controllers/Api/Application/Servers/ServerDetailsController.php b/app/Http/Controllers/Api/Application/Servers/ServerDetailsController.php index bcecf277c0..ae5f5438ca 100644 --- a/app/Http/Controllers/Api/Application/Servers/ServerDetailsController.php +++ b/app/Http/Controllers/Api/Application/Servers/ServerDetailsController.php @@ -12,27 +12,14 @@ class ServerDetailsController extends ApplicationApiController { - /** - * @var \Pterodactyl\Services\Servers\BuildModificationService - */ - private $buildModificationService; - - /** - * @var \Pterodactyl\Services\Servers\DetailsModificationService - */ - private $detailsModificationService; - /** * ServerDetailsController constructor. */ public function __construct( - BuildModificationService $buildModificationService, - DetailsModificationService $detailsModificationService + private BuildModificationService $buildModificationService, + private DetailsModificationService $detailsModificationService ) { parent::__construct(); - - $this->buildModificationService = $buildModificationService; - $this->detailsModificationService = $detailsModificationService; } /** diff --git a/app/Http/Controllers/Api/Application/Servers/ServerManagementController.php b/app/Http/Controllers/Api/Application/Servers/ServerManagementController.php index ddbbbe1ddb..d4dcaa24cb 100644 --- a/app/Http/Controllers/Api/Application/Servers/ServerManagementController.php +++ b/app/Http/Controllers/Api/Application/Servers/ServerManagementController.php @@ -12,26 +12,13 @@ class ServerManagementController extends ApplicationApiController { /** - * @var \Pterodactyl\Services\Servers\ReinstallServerService - */ - private $reinstallServerService; - - /** - * @var \Pterodactyl\Services\Servers\SuspensionService - */ - private $suspensionService; - - /** - * SuspensionController constructor. + * ServerManagementController constructor. */ public function __construct( - ReinstallServerService $reinstallServerService, - SuspensionService $suspensionService + private ReinstallServerService $reinstallServerService, + private SuspensionService $suspensionService ) { parent::__construct(); - - $this->reinstallServerService = $reinstallServerService; - $this->suspensionService = $suspensionService; } /** @@ -41,7 +28,7 @@ public function __construct( */ public function suspend(ServerWriteRequest $request, Server $server): Response { - $this->suspensionService->toggle($server, SuspensionService::ACTION_SUSPEND); + $this->suspensionService->toggle($server); return $this->returnNoContent(); } diff --git a/app/Http/Controllers/Api/Application/Servers/StartupController.php b/app/Http/Controllers/Api/Application/Servers/StartupController.php index 6c854102b7..6c2da078a4 100644 --- a/app/Http/Controllers/Api/Application/Servers/StartupController.php +++ b/app/Http/Controllers/Api/Application/Servers/StartupController.php @@ -11,19 +11,12 @@ class StartupController extends ApplicationApiController { - /** - * @var \Pterodactyl\Services\Servers\StartupModificationService - */ - private $modificationService; - /** * StartupController constructor. */ - public function __construct(StartupModificationService $modificationService) + public function __construct(private StartupModificationService $modificationService) { parent::__construct(); - - $this->modificationService = $modificationService; } /** diff --git a/app/Http/Controllers/Api/Application/Users/UserController.php b/app/Http/Controllers/Api/Application/Users/UserController.php index 0a5675e2d9..0ddae27b1e 100644 --- a/app/Http/Controllers/Api/Application/Users/UserController.php +++ b/app/Http/Controllers/Api/Application/Users/UserController.php @@ -3,13 +3,11 @@ namespace Pterodactyl\Http\Controllers\Api\Application\Users; use Pterodactyl\Models\User; -use Illuminate\Http\Response; use Illuminate\Http\JsonResponse; use Spatie\QueryBuilder\QueryBuilder; use Pterodactyl\Services\Users\UserUpdateService; use Pterodactyl\Services\Users\UserCreationService; use Pterodactyl\Services\Users\UserDeletionService; -use Pterodactyl\Contracts\Repository\UserRepositoryInterface; use Pterodactyl\Transformers\Api\Application\UserTransformer; use Pterodactyl\Http\Requests\Api\Application\Users\GetUsersRequest; use Pterodactyl\Http\Requests\Api\Application\Users\StoreUserRequest; @@ -19,41 +17,15 @@ class UserController extends ApplicationApiController { - /** - * @var \Pterodactyl\Services\Users\UserCreationService - */ - private $creationService; - - /** - * @var \Pterodactyl\Services\Users\UserDeletionService - */ - private $deletionService; - - /** - * @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface - */ - private $repository; - - /** - * @var \Pterodactyl\Services\Users\UserUpdateService - */ - private $updateService; - /** * UserController constructor. */ public function __construct( - UserRepositoryInterface $repository, - UserCreationService $creationService, - UserDeletionService $deletionService, - UserUpdateService $updateService + private UserCreationService $creationService, + private UserDeletionService $deletionService, + private UserUpdateService $updateService ) { parent::__construct(); - - $this->creationService = $creationService; - $this->deletionService = $deletionService; - $this->repository = $repository; - $this->updateService = $updateService; } /** @@ -107,7 +79,7 @@ public function update(UpdateUserRequest $request, User $user): array } /** - * Store a new user on the system. Returns the created user and a HTTP/201 + * Store a new user on the system. Returns the created user and an HTTP/201 * header on successful creation. * * @throws \Exception diff --git a/app/Http/Controllers/Api/Client/AccountController.php b/app/Http/Controllers/Api/Client/AccountController.php index db7a1971dd..cbe8b03156 100644 --- a/app/Http/Controllers/Api/Client/AccountController.php +++ b/app/Http/Controllers/Api/Client/AccountController.php @@ -14,25 +14,12 @@ class AccountController extends ClientApiController { - /** - * @var \Pterodactyl\Services\Users\UserUpdateService - */ - private $updateService; - - /** - * @var \Illuminate\Auth\AuthManager - */ - private $manager; - /** * AccountController constructor. */ - public function __construct(AuthManager $manager, UserUpdateService $updateService) + public function __construct(private AuthManager $manager, private UserUpdateService $updateService) { parent::__construct(); - - $this->updateService = $updateService; - $this->manager = $manager; } public function index(Request $request): array diff --git a/app/Http/Controllers/Api/Client/ApiKeyController.php b/app/Http/Controllers/Api/Client/ApiKeyController.php index c6b6f70922..ac00a4d8f2 100644 --- a/app/Http/Controllers/Api/Client/ApiKeyController.php +++ b/app/Http/Controllers/Api/Client/ApiKeyController.php @@ -13,11 +13,9 @@ class ApiKeyController extends ClientApiController { /** - * Returns all of the API keys that exist for the given client. - * - * @return array + * Returns all the API keys that exist for the given client. */ - public function index(ClientApiRequest $request) + public function index(ClientApiRequest $request): array { return $this->fractal->collection($request->user()->apiKeys) ->transformWith($this->getTransformer(ApiKeyTransformer::class)) @@ -26,6 +24,8 @@ public function index(ClientApiRequest $request) /** * Store a new API key for a user's account. + * + * @throws \Pterodactyl\Exceptions\DisplayException */ public function store(StoreApiKeyRequest $request): array { @@ -51,10 +51,8 @@ public function store(StoreApiKeyRequest $request): array /** * Deletes a given API key. - * - * @return \Illuminate\Http\JsonResponse */ - public function delete(ClientApiRequest $request, string $identifier) + public function delete(ClientApiRequest $request, string $identifier): JsonResponse { /** @var \Pterodactyl\Models\ApiKey $key */ $key = $request->user()->apiKeys() diff --git a/app/Http/Controllers/Api/Client/ClientApiController.php b/app/Http/Controllers/Api/Client/ClientApiController.php index 56e6a17147..a468d762a9 100644 --- a/app/Http/Controllers/Api/Client/ClientApiController.php +++ b/app/Http/Controllers/Api/Client/ClientApiController.php @@ -10,10 +10,8 @@ abstract class ClientApiController extends ApplicationApiController { /** * Returns only the includes which are valid for the given transformer. - * - * @return string[] */ - protected function getIncludesForTransformer(BaseClientTransformer $transformer, array $merge = []) + protected function getIncludesForTransformer(BaseClientTransformer $transformer, array $merge = []): array { $filtered = array_filter($this->parseIncludes(), function ($datum) use ($transformer) { return in_array($datum, $transformer->getAvailableIncludes()); @@ -24,10 +22,8 @@ protected function getIncludesForTransformer(BaseClientTransformer $transformer, /** * Returns the parsed includes for this request. - * - * @return string[] */ - protected function parseIncludes() + protected function parseIncludes(): array { $includes = $this->request->query('include') ?? []; @@ -49,7 +45,6 @@ protected function parseIncludes() * * @return T * - * @noinspection PhpUndefinedClassInspection * @noinspection PhpDocSignatureInspection */ public function getTransformer(string $abstract) diff --git a/app/Http/Controllers/Api/Client/ClientController.php b/app/Http/Controllers/Api/Client/ClientController.php index f83471ff8c..9afb72628f 100644 --- a/app/Http/Controllers/Api/Client/ClientController.php +++ b/app/Http/Controllers/Api/Client/ClientController.php @@ -7,29 +7,21 @@ use Spatie\QueryBuilder\QueryBuilder; use Spatie\QueryBuilder\AllowedFilter; use Pterodactyl\Models\Filters\MultiFieldServerFilter; -use Pterodactyl\Repositories\Eloquent\ServerRepository; use Pterodactyl\Transformers\Api\Client\ServerTransformer; use Pterodactyl\Http\Requests\Api\Client\GetServersRequest; class ClientController extends ClientApiController { - /** - * @var \Pterodactyl\Repositories\Eloquent\ServerRepository - */ - private $repository; - /** * ClientController constructor. */ - public function __construct(ServerRepository $repository) + public function __construct() { parent::__construct(); - - $this->repository = $repository; } /** - * Return all of the servers available to the client making the API + * Return all the servers available to the client making the API * request, including servers the user has access to as a subuser. */ public function index(GetServersRequest $request): array @@ -49,8 +41,8 @@ public function index(GetServersRequest $request): array ]); $type = $request->input('type'); - // Either return all of the servers the user has access to because they are an admin `?type=admin` or - // just return all of the servers the user has access to because they are the owner or a subuser of the + // Either return all the servers the user has access to because they are an admin `?type=admin` or + // just return all the servers the user has access to because they are the owner or a subuser of the // server. If ?type=admin-all is passed all servers on the system will be returned to the user, rather // than only servers they can see because they are an admin. if (in_array($type, ['admin', 'admin-all'])) { @@ -75,11 +67,9 @@ public function index(GetServersRequest $request): array } /** - * Returns all of the subuser permissions available on the system. - * - * @return array + * Returns all the subuser permissions available on the system. */ - public function permissions() + public function permissions(): array { return [ 'object' => 'system_permissions', diff --git a/app/Http/Controllers/Api/Client/SSHKeyController.php b/app/Http/Controllers/Api/Client/SSHKeyController.php index 7d0902d36a..aa0f0c6860 100644 --- a/app/Http/Controllers/Api/Client/SSHKeyController.php +++ b/app/Http/Controllers/Api/Client/SSHKeyController.php @@ -11,7 +11,7 @@ class SSHKeyController extends ClientApiController { /** - * Returns all of the SSH keys that have been configured for the logged in + * Returns all the SSH keys that have been configured for the logged-in * user account. */ public function index(ClientApiRequest $request): array diff --git a/app/Http/Controllers/Api/Client/Servers/BackupController.php b/app/Http/Controllers/Api/Client/Servers/BackupController.php index bfd9b68ba7..7a35341c10 100644 --- a/app/Http/Controllers/Api/Client/Servers/BackupController.php +++ b/app/Http/Controllers/Api/Client/Servers/BackupController.php @@ -21,29 +21,17 @@ class BackupController extends ClientApiController { - private InitiateBackupService $initiateBackupService; - private DeleteBackupService $deleteBackupService; - private DownloadLinkService $downloadLinkService; - private DaemonBackupRepository $daemonRepository; - private BackupRepository $repository; - /** * BackupController constructor. */ public function __construct( - DaemonBackupRepository $daemonRepository, - DeleteBackupService $deleteBackupService, - InitiateBackupService $initiateBackupService, - DownloadLinkService $downloadLinkService, - BackupRepository $repository + private DaemonBackupRepository $daemonRepository, + private DeleteBackupService $deleteBackupService, + private InitiateBackupService $initiateBackupService, + private DownloadLinkService $downloadLinkService, + private BackupRepository $repository ) { parent::__construct(); - - $this->repository = $repository; - $this->initiateBackupService = $initiateBackupService; - $this->deleteBackupService = $deleteBackupService; - $this->downloadLinkService = $downloadLinkService; - $this->daemonRepository = $daemonRepository; } /** @@ -194,9 +182,9 @@ public function download(Request $request, Server $server, Backup $backup): Json * to begin the process of finding (or downloading) the backup and unpacking it * over the server files. * - * If the "truncate" flag is passed through in this request then all of the + * If the "truncate" flag is passed through in this request then all the * files that currently exist on the server will be deleted before restoring. - * Otherwise the archive will simply be unpacked over the existing files. + * Otherwise, the archive will simply be unpacked over the existing files. * * @throws \Throwable */ diff --git a/app/Http/Controllers/Api/Client/Servers/CommandController.php b/app/Http/Controllers/Api/Client/Servers/CommandController.php index 05be19c604..6cc50de473 100644 --- a/app/Http/Controllers/Api/Client/Servers/CommandController.php +++ b/app/Http/Controllers/Api/Client/Servers/CommandController.php @@ -15,19 +15,12 @@ class CommandController extends ClientApiController { - /** - * @var \Pterodactyl\Repositories\Wings\DaemonCommandRepository - */ - private $repository; - /** * CommandController constructor. */ - public function __construct(DaemonCommandRepository $repository) + public function __construct(private DaemonCommandRepository $repository) { parent::__construct(); - - $this->repository = $repository; } /** diff --git a/app/Http/Controllers/Api/Client/Servers/DatabaseController.php b/app/Http/Controllers/Api/Client/Servers/DatabaseController.php index 34f2139f68..728c67e913 100644 --- a/app/Http/Controllers/Api/Client/Servers/DatabaseController.php +++ b/app/Http/Controllers/Api/Client/Servers/DatabaseController.php @@ -6,7 +6,6 @@ use Pterodactyl\Models\Server; use Pterodactyl\Models\Database; use Pterodactyl\Facades\Activity; -use Pterodactyl\Repositories\Eloquent\DatabaseRepository; use Pterodactyl\Services\Databases\DatabasePasswordService; use Pterodactyl\Transformers\Api\Client\DatabaseTransformer; use Pterodactyl\Services\Databases\DatabaseManagementService; @@ -19,45 +18,19 @@ class DatabaseController extends ClientApiController { - /** - * @var \Pterodactyl\Services\Databases\DeployServerDatabaseService - */ - private $deployDatabaseService; - - /** - * @var \Pterodactyl\Repositories\Eloquent\DatabaseRepository - */ - private $repository; - - /** - * @var \Pterodactyl\Services\Databases\DatabaseManagementService - */ - private $managementService; - - /** - * @var \Pterodactyl\Services\Databases\DatabasePasswordService - */ - private $passwordService; - /** * DatabaseController constructor. */ public function __construct( - DatabaseManagementService $managementService, - DatabasePasswordService $passwordService, - DatabaseRepository $repository, - DeployServerDatabaseService $deployDatabaseService + private DeployServerDatabaseService $deployDatabaseService, + private DatabaseManagementService $managementService, + private DatabasePasswordService $passwordService ) { parent::__construct(); - - $this->deployDatabaseService = $deployDatabaseService; - $this->repository = $repository; - $this->managementService = $managementService; - $this->passwordService = $passwordService; } /** - * Return all of the databases that belong to the given server. + * Return all the databases that belong to the given server. */ public function index(GetDatabasesRequest $request, Server $server): array { @@ -92,11 +65,9 @@ public function store(StoreDatabaseRequest $request, Server $server): array * Rotates the password for the given server model and returns a fresh instance to * the caller. * - * @return array - * * @throws \Throwable */ - public function rotatePassword(RotatePasswordRequest $request, Server $server, Database $database) + public function rotatePassword(RotatePasswordRequest $request, Server $server, Database $database): array { $this->passwordService->handle($database); $database->refresh(); @@ -126,6 +97,6 @@ public function delete(DeleteDatabaseRequest $request, Server $server, Database ->property('name', $database->database) ->log(); - return Response::create('', Response::HTTP_NO_CONTENT); + return new Response('', Response::HTTP_NO_CONTENT); } } diff --git a/app/Http/Controllers/Api/Client/Servers/FileController.php b/app/Http/Controllers/Api/Client/Servers/FileController.php index 68e1ec086a..23e6718a50 100644 --- a/app/Http/Controllers/Api/Client/Servers/FileController.php +++ b/app/Http/Controllers/Api/Client/Servers/FileController.php @@ -25,27 +25,14 @@ class FileController extends ClientApiController { - /** - * @var \Pterodactyl\Repositories\Wings\DaemonFileRepository - */ - private $fileRepository; - - /** - * @var \Pterodactyl\Services\Nodes\NodeJWTService - */ - private $jwtService; - /** * FileController constructor. */ public function __construct( - NodeJWTService $jwtService, - DaemonFileRepository $fileRepository + private NodeJWTService $jwtService, + private DaemonFileRepository $fileRepository ) { parent::__construct(); - - $this->fileRepository = $fileRepository; - $this->jwtService = $jwtService; } /** @@ -85,11 +72,9 @@ public function contents(GetFileContentsRequest $request, Server $server): Respo * Generates a one-time token with a link that the user can use to * download a given file. * - * @return array - * * @throws \Throwable */ - public function download(GetFileContentsRequest $request, Server $server) + public function download(GetFileContentsRequest $request, Server $server): array { $token = $this->jwtService ->setExpiresAt(CarbonImmutable::now()->addMinutes(15)) diff --git a/app/Http/Controllers/Api/Client/Servers/FileUploadController.php b/app/Http/Controllers/Api/Client/Servers/FileUploadController.php index 710315bddf..98fcd587dc 100644 --- a/app/Http/Controllers/Api/Client/Servers/FileUploadController.php +++ b/app/Http/Controllers/Api/Client/Servers/FileUploadController.php @@ -12,28 +12,19 @@ class FileUploadController extends ClientApiController { - /** - * @var \Pterodactyl\Services\Nodes\NodeJWTService - */ - private $jwtService; - /** * FileUploadController constructor. */ public function __construct( - NodeJWTService $jwtService + private NodeJWTService $jwtService ) { parent::__construct(); - - $this->jwtService = $jwtService; } /** - * Returns a url where files can be uploaded to. - * - * @return \Illuminate\Http\JsonResponse + * Returns an url where files can be uploaded to. */ - public function __invoke(UploadFileRequest $request, Server $server) + public function __invoke(UploadFileRequest $request, Server $server): JsonResponse { return new JsonResponse([ 'object' => 'signed_url', @@ -44,11 +35,9 @@ public function __invoke(UploadFileRequest $request, Server $server) } /** - * Returns a url where files can be uploaded to. - * - * @return string + * Returns an url where files can be uploaded to. */ - protected function getUploadUrl(Server $server, User $user) + protected function getUploadUrl(Server $server, User $user): string { $token = $this->jwtService ->setExpiresAt(CarbonImmutable::now()->addMinutes(15)) diff --git a/app/Http/Controllers/Api/Client/Servers/NetworkAllocationController.php b/app/Http/Controllers/Api/Client/Servers/NetworkAllocationController.php index 80455e0f81..4e3c5f9bbd 100644 --- a/app/Http/Controllers/Api/Client/Servers/NetworkAllocationController.php +++ b/app/Http/Controllers/Api/Client/Servers/NetworkAllocationController.php @@ -8,7 +8,6 @@ use Pterodactyl\Models\Allocation; use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Repositories\Eloquent\ServerRepository; -use Pterodactyl\Repositories\Eloquent\AllocationRepository; use Pterodactyl\Transformers\Api\Client\AllocationTransformer; use Pterodactyl\Http\Controllers\Api\Client\ClientApiController; use Pterodactyl\Services\Allocations\FindAssignableAllocationService; @@ -21,38 +20,18 @@ class NetworkAllocationController extends ClientApiController { /** - * @var \Pterodactyl\Repositories\Eloquent\AllocationRepository - */ - private $repository; - - /** - * @var \Pterodactyl\Repositories\Eloquent\ServerRepository - */ - private $serverRepository; - - /** - * @var \Pterodactyl\Services\Allocations\FindAssignableAllocationService - */ - private $assignableAllocationService; - - /** - * NetworkController constructor. + * NetworkAllocationController constructor. */ public function __construct( - AllocationRepository $repository, - ServerRepository $serverRepository, - FindAssignableAllocationService $assignableAllocationService + private FindAssignableAllocationService $assignableAllocationService, + private ServerRepository $serverRepository ) { parent::__construct(); - - $this->repository = $repository; - $this->serverRepository = $serverRepository; - $this->assignableAllocationService = $assignableAllocationService; } /** - * Lists all of the allocations available to a server and wether or - * not they are currently assigned as the primary for this server. + * Lists all the allocations available to a server and whether + * they are currently assigned as the primary for this server. */ public function index(GetNetworkRequest $request, Server $server): array { @@ -132,11 +111,9 @@ public function store(NewAllocationRequest $request, Server $server): array /** * Delete an allocation from a server. * - * @return \Illuminate\Http\JsonResponse - * * @throws \Pterodactyl\Exceptions\DisplayException */ - public function delete(DeleteAllocationRequest $request, Server $server, Allocation $allocation) + public function delete(DeleteAllocationRequest $request, Server $server, Allocation $allocation): JsonResponse { // Don't allow the deletion of allocations if the server does not have an // allocation limit set. diff --git a/app/Http/Controllers/Api/Client/Servers/PowerController.php b/app/Http/Controllers/Api/Client/Servers/PowerController.php index 01d1c1349e..ca05757651 100644 --- a/app/Http/Controllers/Api/Client/Servers/PowerController.php +++ b/app/Http/Controllers/Api/Client/Servers/PowerController.php @@ -11,19 +11,12 @@ class PowerController extends ClientApiController { - /** - * @var \Pterodactyl\Repositories\Wings\DaemonPowerRepository - */ - private $repository; - /** * PowerController constructor. */ - public function __construct(DaemonPowerRepository $repository) + public function __construct(private DaemonPowerRepository $repository) { parent::__construct(); - - $this->repository = $repository; } /** diff --git a/app/Http/Controllers/Api/Client/Servers/ResourceUtilizationController.php b/app/Http/Controllers/Api/Client/Servers/ResourceUtilizationController.php index d28622392b..dcaf481156 100644 --- a/app/Http/Controllers/Api/Client/Servers/ResourceUtilizationController.php +++ b/app/Http/Controllers/Api/Client/Servers/ResourceUtilizationController.php @@ -12,19 +12,12 @@ class ResourceUtilizationController extends ClientApiController { - private DaemonServerRepository $repository; - - private Repository $cache; - /** * ResourceUtilizationController constructor. */ - public function __construct(Repository $cache, DaemonServerRepository $repository) + public function __construct(private Repository $cache, private DaemonServerRepository $repository) { parent::__construct(); - - $this->cache = $cache; - $this->repository = $repository; } /** @@ -36,7 +29,7 @@ public function __construct(Repository $cache, DaemonServerRepository $repositor */ public function __invoke(GetServerRequest $request, Server $server): array { - $key = "resources:{$server->uuid}"; + $key = "resources:$server->uuid"; $stats = $this->cache->remember($key, Carbon::now()->addSeconds(20), function () use ($server) { return $this->repository->setServer($server)->getDetails(); }); diff --git a/app/Http/Controllers/Api/Client/Servers/ScheduleController.php b/app/Http/Controllers/Api/Client/Servers/ScheduleController.php index eb92bb0028..50c04c70fd 100644 --- a/app/Http/Controllers/Api/Client/Servers/ScheduleController.php +++ b/app/Http/Controllers/Api/Client/Servers/ScheduleController.php @@ -25,33 +25,18 @@ class ScheduleController extends ClientApiController { - /** - * @var \Pterodactyl\Repositories\Eloquent\ScheduleRepository - */ - private $repository; - - /** - * @var \Pterodactyl\Services\Schedules\ProcessScheduleService - */ - private $service; - /** * ScheduleController constructor. */ - public function __construct(ScheduleRepository $repository, ProcessScheduleService $service) + public function __construct(private ScheduleRepository $repository, private ProcessScheduleService $service) { parent::__construct(); - - $this->repository = $repository; - $this->service = $service; } /** - * Returns all of the schedules belonging to a given server. - * - * @return array + * Returns all the schedules belonging to a given server. */ - public function index(ViewScheduleRequest $request, Server $server) + public function index(ViewScheduleRequest $request, Server $server): array { $schedules = $server->schedules->loadMissing('tasks'); @@ -63,12 +48,10 @@ public function index(ViewScheduleRequest $request, Server $server) /** * Store a new schedule for a server. * - * @return array - * * @throws \Pterodactyl\Exceptions\DisplayException * @throws \Pterodactyl\Exceptions\Model\DataValidationException */ - public function store(StoreScheduleRequest $request, Server $server) + public function store(StoreScheduleRequest $request, Server $server): array { /** @var \Pterodactyl\Models\Schedule $model */ $model = $this->repository->create([ @@ -96,10 +79,8 @@ public function store(StoreScheduleRequest $request, Server $server) /** * Returns a specific schedule for the server. - * - * @return array */ - public function view(ViewScheduleRequest $request, Server $server, Schedule $schedule) + public function view(ViewScheduleRequest $request, Server $server, Schedule $schedule): array { if ($schedule->server_id !== $server->id) { throw new NotFoundHttpException(); @@ -115,13 +96,11 @@ public function view(ViewScheduleRequest $request, Server $server, Schedule $sch /** * Updates a given schedule with the new data provided. * - * @return array - * * @throws \Pterodactyl\Exceptions\DisplayException * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function update(UpdateScheduleRequest $request, Server $server, Schedule $schedule) + public function update(UpdateScheduleRequest $request, Server $server, Schedule $schedule): array { $active = (bool) $request->input('is_active'); @@ -161,11 +140,9 @@ public function update(UpdateScheduleRequest $request, Server $server, Schedule * Executes a given schedule immediately rather than waiting on it's normally scheduled time * to pass. This does not care about the schedule state. * - * @return \Illuminate\Http\JsonResponse - * * @throws \Throwable */ - public function execute(TriggerScheduleRequest $request, Server $server, Schedule $schedule) + public function execute(TriggerScheduleRequest $request, Server $server, Schedule $schedule): JsonResponse { $this->service->handle($schedule, true); @@ -176,10 +153,8 @@ public function execute(TriggerScheduleRequest $request, Server $server, Schedul /** * Deletes a schedule and it's associated tasks. - * - * @return \Illuminate\Http\JsonResponse */ - public function delete(DeleteScheduleRequest $request, Server $server, Schedule $schedule) + public function delete(DeleteScheduleRequest $request, Server $server, Schedule $schedule): JsonResponse { $this->repository->delete($schedule->id); diff --git a/app/Http/Controllers/Api/Client/Servers/ScheduleTaskController.php b/app/Http/Controllers/Api/Client/Servers/ScheduleTaskController.php index 75bb48d820..addca2876b 100644 --- a/app/Http/Controllers/Api/Client/Servers/ScheduleTaskController.php +++ b/app/Http/Controllers/Api/Client/Servers/ScheduleTaskController.php @@ -20,35 +20,25 @@ class ScheduleTaskController extends ClientApiController { - /** - * @var \Pterodactyl\Repositories\Eloquent\TaskRepository - */ - private $repository; - /** * ScheduleTaskController constructor. */ - public function __construct(TaskRepository $repository) + public function __construct(private TaskRepository $repository) { parent::__construct(); - - $this->repository = $repository; } /** * Create a new task for a given schedule and store it in the database. * - * @return array - * - * @throws \Pterodactyl\Exceptions\Model\HttpForbiddenException * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Service\ServiceLimitExceededException */ - public function store(StoreTaskRequest $request, Server $server, Schedule $schedule) + public function store(StoreTaskRequest $request, Server $server, Schedule $schedule): array { $limit = config('pterodactyl.client_features.schedules.per_schedule_task_limit', 10); if ($schedule->tasks()->count() >= $limit) { - throw new ServiceLimitExceededException("Schedules may not have more than {$limit} tasks associated with them. Creating this task would put this schedule over the limit."); + 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') { @@ -81,13 +71,10 @@ public function store(StoreTaskRequest $request, Server $server, Schedule $sched /** * Updates a given task for a server. * - * @return array - * - * @throws \Pterodactyl\Exceptions\Model\HttpForbiddenException * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function update(StoreTaskRequest $request, Server $server, Schedule $schedule, Task $task) + public function update(StoreTaskRequest $request, Server $server, Schedule $schedule, Task $task): array { if ($schedule->id !== $task->schedule_id || $server->id !== $schedule->server_id) { throw new NotFoundHttpException(); @@ -118,11 +105,9 @@ public function update(StoreTaskRequest $request, Server $server, Schedule $sche * Delete a given task for a schedule. If there are subsequent tasks stored in the database * for this schedule their sequence IDs are decremented properly. * - * @return \Illuminate\Http\JsonResponse - * * @throws \Exception */ - public function delete(ClientApiRequest $request, Server $server, Schedule $schedule, Task $task) + public function delete(ClientApiRequest $request, Server $server, Schedule $schedule, Task $task): JsonResponse { if ($task->schedule_id !== $schedule->id || $schedule->server_id !== $server->id) { throw new NotFoundHttpException(); diff --git a/app/Http/Controllers/Api/Client/Servers/ServerController.php b/app/Http/Controllers/Api/Client/Servers/ServerController.php index 82091c48b0..63eb9b9889 100644 --- a/app/Http/Controllers/Api/Client/Servers/ServerController.php +++ b/app/Http/Controllers/Api/Client/Servers/ServerController.php @@ -3,7 +3,6 @@ namespace Pterodactyl\Http\Controllers\Api\Client\Servers; use Pterodactyl\Models\Server; -use Pterodactyl\Repositories\Eloquent\SubuserRepository; use Pterodactyl\Transformers\Api\Client\ServerTransformer; use Pterodactyl\Services\Servers\GetUserPermissionsService; use Pterodactyl\Http\Controllers\Api\Client\ClientApiController; @@ -11,25 +10,12 @@ class ServerController extends ClientApiController { - /** - * @var \Pterodactyl\Repositories\Eloquent\SubuserRepository - */ - private $repository; - - /** - * @var \Pterodactyl\Services\Servers\GetUserPermissionsService - */ - private $permissionsService; - /** * ServerController constructor. */ - public function __construct(GetUserPermissionsService $permissionsService, SubuserRepository $repository) + public function __construct(private GetUserPermissionsService $permissionsService) { parent::__construct(); - - $this->repository = $repository; - $this->permissionsService = $permissionsService; } /** diff --git a/app/Http/Controllers/Api/Client/Servers/SettingsController.php b/app/Http/Controllers/Api/Client/Servers/SettingsController.php index 883362b79c..7f1946a212 100644 --- a/app/Http/Controllers/Api/Client/Servers/SettingsController.php +++ b/app/Http/Controllers/Api/Client/Servers/SettingsController.php @@ -16,38 +16,23 @@ class SettingsController extends ClientApiController { - /** - * @var \Pterodactyl\Repositories\Eloquent\ServerRepository - */ - private $repository; - - /** - * @var \Pterodactyl\Services\Servers\ReinstallServerService - */ - private $reinstallServerService; - /** * SettingsController constructor. */ public function __construct( - ServerRepository $repository, - ReinstallServerService $reinstallServerService + private ServerRepository $repository, + private ReinstallServerService $reinstallServerService ) { parent::__construct(); - - $this->repository = $repository; - $this->reinstallServerService = $reinstallServerService; } /** * Renames a server. * - * @return \Illuminate\Http\JsonResponse - * * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function rename(RenameServerRequest $request, Server $server) + public function rename(RenameServerRequest $request, Server $server): JsonResponse { $this->repository->update($server->id, [ 'name' => $request->input('name'), @@ -65,11 +50,9 @@ public function rename(RenameServerRequest $request, Server $server) /** * Reinstalls the server on the daemon. * - * @return \Illuminate\Http\JsonResponse - * * @throws \Throwable */ - public function reinstall(ReinstallServerRequest $request, Server $server) + public function reinstall(ReinstallServerRequest $request, Server $server): JsonResponse { $this->reinstallServerService->handle($server); @@ -81,11 +64,9 @@ public function reinstall(ReinstallServerRequest $request, Server $server) /** * Changes the Docker image in use by the server. * - * @return \Illuminate\Http\JsonResponse - * * @throws \Throwable */ - public function dockerImage(SetDockerImageRequest $request, Server $server) + public function dockerImage(SetDockerImageRequest $request, Server $server): JsonResponse { if (!in_array($server->image, array_values($server->egg->docker_images))) { throw new BadRequestHttpException('This server\'s Docker image has been manually set by an administrator and cannot be updated.'); diff --git a/app/Http/Controllers/Api/Client/Servers/StartupController.php b/app/Http/Controllers/Api/Client/Servers/StartupController.php index 78194affdc..9548be25e9 100644 --- a/app/Http/Controllers/Api/Client/Servers/StartupController.php +++ b/app/Http/Controllers/Api/Client/Servers/StartupController.php @@ -5,7 +5,6 @@ use Pterodactyl\Models\Server; use Pterodactyl\Facades\Activity; use Pterodactyl\Services\Servers\StartupCommandService; -use Pterodactyl\Services\Servers\VariableValidatorService; use Pterodactyl\Repositories\Eloquent\ServerVariableRepository; use Pterodactyl\Transformers\Api\Client\EggVariableTransformer; use Pterodactyl\Http\Controllers\Api\Client\ClientApiController; @@ -15,41 +14,22 @@ class StartupController extends ClientApiController { - /** - * @var \Pterodactyl\Services\Servers\VariableValidatorService - */ - private $service; - - /** - * @var \Pterodactyl\Repositories\Eloquent\ServerVariableRepository - */ - private $repository; - - /** - * @var \Pterodactyl\Services\Servers\StartupCommandService - */ - private $startupCommandService; - /** * StartupController constructor. */ - public function __construct(VariableValidatorService $service, StartupCommandService $startupCommandService, ServerVariableRepository $repository) - { + public function __construct( + private StartupCommandService $startupCommandService, + private ServerVariableRepository $repository + ) { parent::__construct(); - - $this->service = $service; - $this->repository = $repository; - $this->startupCommandService = $startupCommandService; } /** - * Returns the startup information for the server including all of the variables. - * - * @return array + * Returns the startup information for the server including all the variables. */ - public function index(GetStartupRequest $request, Server $server) + public function index(GetStartupRequest $request, Server $server): array { - $startup = $this->startupCommandService->handle($server, false); + $startup = $this->startupCommandService->handle($server); return $this->fractal->collection( $server->variables()->where('user_viewable', true)->get() @@ -66,13 +46,11 @@ public function index(GetStartupRequest $request, Server $server) /** * Updates a single variable for a server. * - * @return array - * * @throws \Illuminate\Validation\ValidationException * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function update(UpdateStartupVariableRequest $request, Server $server) + public function update(UpdateStartupVariableRequest $request, Server $server): array { /** @var \Pterodactyl\Models\EggVariable $variable */ $variable = $server->variables()->where('env_variable', $request->input('key'))->first(); @@ -97,7 +75,7 @@ public function update(UpdateStartupVariableRequest $request, Server $server) $variable = $variable->refresh(); $variable->server_value = $request->input('value'); - $startup = $this->startupCommandService->handle($server, false); + $startup = $this->startupCommandService->handle($server); if ($variable->env_variable !== $request->input('value')) { Activity::event('server:startup.edit') diff --git a/app/Http/Controllers/Api/Client/Servers/SubuserController.php b/app/Http/Controllers/Api/Client/Servers/SubuserController.php index dfa503e484..2c403c691d 100644 --- a/app/Http/Controllers/Api/Client/Servers/SubuserController.php +++ b/app/Http/Controllers/Api/Client/Servers/SubuserController.php @@ -21,42 +21,21 @@ class SubuserController extends ClientApiController { - /** - * @var \Pterodactyl\Repositories\Eloquent\SubuserRepository - */ - private $repository; - - /** - * @var \Pterodactyl\Services\Subusers\SubuserCreationService - */ - private $creationService; - - /** - * @var \Pterodactyl\Repositories\Wings\DaemonServerRepository - */ - private $serverRepository; - /** * SubuserController constructor. */ public function __construct( - SubuserRepository $repository, - SubuserCreationService $creationService, - DaemonServerRepository $serverRepository + private SubuserRepository $repository, + private SubuserCreationService $creationService, + private DaemonServerRepository $serverRepository ) { parent::__construct(); - - $this->repository = $repository; - $this->creationService = $creationService; - $this->serverRepository = $serverRepository; } /** * Return the users associated with this server instance. - * - * @return array */ - public function index(GetSubuserRequest $request, Server $server) + public function index(GetSubuserRequest $request, Server $server): array { return $this->fractal->collection($server->subusers) ->transformWith($this->getTransformer(SubuserTransformer::class)) @@ -65,10 +44,8 @@ public function index(GetSubuserRequest $request, Server $server) /** * Returns a single subuser associated with this server instance. - * - * @return array */ - public function view(GetSubuserRequest $request) + public function view(GetSubuserRequest $request): array { $subuser = $request->attributes->get('subuser'); @@ -80,14 +57,12 @@ public function view(GetSubuserRequest $request) /** * Create a new subuser for the given server. * - * @return array - * * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Service\Subuser\ServerSubuserExistsException * @throws \Pterodactyl\Exceptions\Service\Subuser\UserIsServerOwnerException * @throws \Throwable */ - public function store(StoreSubuserRequest $request, Server $server) + public function store(StoreSubuserRequest $request, Server $server): array { $response = $this->creationService->handle( $server, @@ -143,7 +118,7 @@ public function update(UpdateSubuserRequest $request, Server $server): array $this->serverRepository->setServer($server)->revokeUserJTI($subuser->user_id); } catch (DaemonConnectionException $exception) { // Don't block this request if we can't connect to the Wings instance. Chances are it is - // offline in this event and the token will be invalid anyways once Wings boots back. + // offline and the token will be invalid once Wings boots back. Log::warning($exception, ['user_id' => $subuser->user_id, 'server_id' => $server->id]); $instance->property('revoked', false); @@ -160,10 +135,8 @@ public function update(UpdateSubuserRequest $request, Server $server): array /** * Removes a subusers from a server's assignment. - * - * @return \Illuminate\Http\JsonResponse */ - public function delete(DeleteSubuserRequest $request, Server $server) + public function delete(DeleteSubuserRequest $request, Server $server): JsonResponse { /** @var \Pterodactyl\Models\Subuser $subuser */ $subuser = $request->attributes->get('subuser'); diff --git a/app/Http/Controllers/Api/Client/Servers/WebsocketController.php b/app/Http/Controllers/Api/Client/Servers/WebsocketController.php index 5446b22ce7..59b6f75d4d 100644 --- a/app/Http/Controllers/Api/Client/Servers/WebsocketController.php +++ b/app/Http/Controllers/Api/Client/Servers/WebsocketController.php @@ -14,38 +14,23 @@ class WebsocketController extends ClientApiController { - /** - * @var \Pterodactyl\Services\Nodes\NodeJWTService - */ - private $jwtService; - - /** - * @var \Pterodactyl\Services\Servers\GetUserPermissionsService - */ - private $permissionsService; - /** * WebsocketController constructor. */ public function __construct( - NodeJWTService $jwtService, - GetUserPermissionsService $permissionsService + private NodeJWTService $jwtService, + private GetUserPermissionsService $permissionsService ) { parent::__construct(); - - $this->jwtService = $jwtService; - $this->permissionsService = $permissionsService; } /** * Generates a one-time token that is sent along in every websocket call to the Daemon. - * This is a signed JWT that the Daemon then uses the verify the user's identity, and - * allows us to continually renew this token and avoid users mainitaining sessions wrongly, + * This is a signed JWT that the Daemon then uses to verify the user's identity, and + * allows us to continually renew this token and avoid users maintaining sessions wrongly, * as well as ensure that user's only perform actions they're allowed to. - * - * @return \Illuminate\Http\JsonResponse */ - public function __invoke(ClientApiRequest $request, Server $server) + public function __invoke(ClientApiRequest $request, Server $server): JsonResponse { $user = $request->user(); if ($user->cannot(Permission::ACTION_WEBSOCKET_CONNECT, $server)) { diff --git a/app/Http/Controllers/Api/Client/TwoFactorController.php b/app/Http/Controllers/Api/Client/TwoFactorController.php index 74857cd7a9..8cfa09b6eb 100644 --- a/app/Http/Controllers/Api/Client/TwoFactorController.php +++ b/app/Http/Controllers/Api/Client/TwoFactorController.php @@ -7,41 +7,22 @@ use Illuminate\Http\Response; use Illuminate\Http\JsonResponse; use Pterodactyl\Facades\Activity; -use Illuminate\Contracts\Validation\Factory; use Pterodactyl\Services\Users\TwoFactorSetupService; use Pterodactyl\Services\Users\ToggleTwoFactorService; +use Illuminate\Contracts\Validation\Factory as ValidationFactory; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; class TwoFactorController extends ClientApiController { - /** - * @var \Pterodactyl\Services\Users\TwoFactorSetupService - */ - private $setupService; - - /** - * @var \Illuminate\Contracts\Validation\Factory - */ - private $validation; - - /** - * @var \Pterodactyl\Services\Users\ToggleTwoFactorService - */ - private $toggleTwoFactorService; - /** * TwoFactorController constructor. */ public function __construct( - ToggleTwoFactorService $toggleTwoFactorService, - TwoFactorSetupService $setupService, - Factory $validation + private ToggleTwoFactorService $toggleTwoFactorService, + private TwoFactorSetupService $setupService, + private ValidationFactory $validation ) { parent::__construct(); - - $this->setupService = $setupService; - $this->validation = $validation; - $this->toggleTwoFactorService = $toggleTwoFactorService; } /** @@ -49,12 +30,10 @@ public function __construct( * it on their account. If two-factor is already enabled this endpoint * will return a 400 error. * - * @return \Illuminate\Http\JsonResponse - * * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function index(Request $request) + public function index(Request $request): JsonResponse { if ($request->user()->use_totp) { throw new BadRequestHttpException('Two-factor authentication is already enabled on this account.'); @@ -68,12 +47,10 @@ public function index(Request $request) /** * Updates a user's account to have two-factor enabled. * - * @return \Illuminate\Http\JsonResponse - * * @throws \Throwable * @throws \Illuminate\Validation\ValidationException */ - public function store(Request $request) + public function store(Request $request): JsonResponse { $validator = $this->validation->make($request->all(), [ 'code' => ['required', 'string', 'size:6'], @@ -101,10 +78,9 @@ public function store(Request $request) * Disables two-factor authentication on an account if the password provided * is valid. * - * @return \Illuminate\Http\JsonResponse * @throws \Throwable */ - public function delete(Request $request) + public function delete(Request $request): JsonResponse { if (!password_verify($request->input('password') ?? '', $request->user()->password)) { throw new BadRequestHttpException('The password provided was not valid.'); diff --git a/app/Http/Controllers/Api/Remote/ActivityProcessingController.php b/app/Http/Controllers/Api/Remote/ActivityProcessingController.php index 04a844888e..ac9dd54161 100644 --- a/app/Http/Controllers/Api/Remote/ActivityProcessingController.php +++ b/app/Http/Controllers/Api/Remote/ActivityProcessingController.php @@ -4,12 +4,13 @@ use Exception; use Carbon\Carbon; +use DateTimeInterface; use Illuminate\Support\Str; use Pterodactyl\Models\User; use Webmozart\Assert\Assert; use Pterodactyl\Models\Server; -use Pterodactyl\Models\ActivityLog; use Illuminate\Support\Facades\Log; +use Pterodactyl\Models\ActivityLog; use Pterodactyl\Models\ActivityLogSubject; use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Http\Requests\Api\Remote\ActivityEventRequest; @@ -36,7 +37,7 @@ public function __invoke(ActivityEventRequest $request) try { $when = Carbon::createFromFormat( - Carbon::RFC3339, + DateTimeInterface::RFC3339, preg_replace('/(\.\d+)Z$/', 'Z', $datum['timestamp']), 'UTC' ); diff --git a/app/Http/Controllers/Api/Remote/Backups/BackupRemoteUploadController.php b/app/Http/Controllers/Api/Remote/Backups/BackupRemoteUploadController.php index a1ffb81bc6..7d92e0b1aa 100644 --- a/app/Http/Controllers/Api/Remote/Backups/BackupRemoteUploadController.php +++ b/app/Http/Controllers/Api/Remote/Backups/BackupRemoteUploadController.php @@ -6,10 +6,9 @@ use Illuminate\Http\Request; use Pterodactyl\Models\Backup; use Illuminate\Http\JsonResponse; -use League\Flysystem\AwsS3v3\AwsS3Adapter; use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Extensions\Backups\BackupManager; -use Pterodactyl\Repositories\Eloquent\BackupRepository; +use Pterodactyl\Extensions\Filesystem\S3Filesystem; use Symfony\Component\HttpKernel\Exception\ConflictHttpException; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; @@ -17,35 +16,21 @@ class BackupRemoteUploadController extends Controller { public const DEFAULT_MAX_PART_SIZE = 5 * 1024 * 1024 * 1024; - /** - * @var \Pterodactyl\Repositories\Eloquent\BackupRepository - */ - private $repository; - - /** - * @var \Pterodactyl\Extensions\Backups\BackupManager - */ - private $backupManager; - /** * BackupRemoteUploadController constructor. */ - public function __construct(BackupRepository $repository, BackupManager $backupManager) + public function __construct(private BackupManager $backupManager) { - $this->repository = $repository; - $this->backupManager = $backupManager; } /** * Returns the required presigned urls to upload a backup to S3 cloud storage. * - * @return \Illuminate\Http\JsonResponse - * * @throws \Exception * @throws \Throwable * @throws \Illuminate\Database\Eloquent\ModelNotFoundException */ - public function __invoke(Request $request, string $backup) + public function __invoke(Request $request, string $backup): JsonResponse { // Get the size query parameter. $size = (int) $request->query('size'); @@ -64,7 +49,7 @@ public function __invoke(Request $request, string $backup) // Ensure we are using the S3 adapter. $adapter = $this->backupManager->adapter(); - if (!$adapter instanceof AwsS3Adapter) { + if (!$adapter instanceof S3Filesystem) { throw new BadRequestHttpException('The configured backup adapter is not an S3 compatible adapter.'); } @@ -116,7 +101,7 @@ public function __invoke(Request $request, string $backup) } /** - * Get the configured maximum size of a single part in the multipart uplaod. + * Get the configured maximum size of a single part in the multipart upload. * * The function tries to retrieve a configured value from the configuration. * If no value is specified, a fallback value will be used. @@ -125,10 +110,8 @@ public function __invoke(Request $request, string $backup) * the fallback value will be used too. * * The fallback value is {@see BackupRemoteUploadController::DEFAULT_MAX_PART_SIZE}. - * - * @return int */ - private function getConfiguredMaxPartSize() + private function getConfiguredMaxPartSize(): int { $maxPartSize = (int) config('backups.max_part_size', self::DEFAULT_MAX_PART_SIZE); if ($maxPartSize <= 0) { diff --git a/app/Http/Controllers/Api/Remote/Backups/BackupStatusController.php b/app/Http/Controllers/Api/Remote/Backups/BackupStatusController.php index 93ebc74a8f..f9c2a7932e 100644 --- a/app/Http/Controllers/Api/Remote/Backups/BackupStatusController.php +++ b/app/Http/Controllers/Api/Remote/Backups/BackupStatusController.php @@ -7,36 +7,28 @@ use Pterodactyl\Models\Backup; use Illuminate\Http\JsonResponse; use Pterodactyl\Facades\Activity; -use League\Flysystem\AwsS3v3\AwsS3Adapter; use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Extensions\Backups\BackupManager; +use Pterodactyl\Extensions\Filesystem\S3Filesystem; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; use Pterodactyl\Http\Requests\Api\Remote\ReportBackupCompleteRequest; class BackupStatusController extends Controller { - /** - * @var \Pterodactyl\Extensions\Backups\BackupManager - */ - private $backupManager; - /** * BackupStatusController constructor. */ - public function __construct(BackupManager $backupManager) + public function __construct(private BackupManager $backupManager) { - $this->backupManager = $backupManager; } /** * Handles updating the state of a backup. * - * @return \Illuminate\Http\JsonResponse - * * @throws \Throwable */ - public function index(ReportBackupCompleteRequest $request, string $backup) + public function index(ReportBackupCompleteRequest $request, string $backup): JsonResponse { /** @var \Pterodactyl\Models\Backup $model */ $model = Backup::query()->where('uuid', $backup)->firstOrFail(); @@ -65,7 +57,7 @@ public function index(ReportBackupCompleteRequest $request, string $backup) // Check if we are using the s3 backup adapter. If so, make sure we mark the backup as // being completed in S3 correctly. $adapter = $this->backupManager->adapter(); - if ($adapter instanceof AwsS3Adapter) { + if ($adapter instanceof S3Filesystem) { $this->completeMultipartUpload($model, $adapter, $successful, $request->input('parts')); } }); @@ -81,8 +73,6 @@ public function index(ReportBackupCompleteRequest $request, string $backup) * The only thing the successful field does is update the entry value for the audit logs * table tracking for this restoration. * - * @return \Illuminate\Http\JsonResponse - * * @throws \Throwable */ public function restore(Request $request, string $backup): JsonResponse @@ -107,7 +97,7 @@ public function restore(Request $request, string $backup): JsonResponse * @throws \Exception * @throws \Pterodactyl\Exceptions\DisplayException */ - protected function completeMultipartUpload(Backup $backup, AwsS3Adapter $adapter, bool $successful, ?array $parts): void + protected function completeMultipartUpload(Backup $backup, S3Filesystem $adapter, bool $successful, ?array $parts): void { // This should never really happen, but if it does don't let us fall victim to Amazon's // wildly fun error messaging. Just stop the process right here. diff --git a/app/Http/Controllers/Api/Remote/EggInstallController.php b/app/Http/Controllers/Api/Remote/EggInstallController.php index e1f804be23..31df7a96ce 100644 --- a/app/Http/Controllers/Api/Remote/EggInstallController.php +++ b/app/Http/Controllers/Api/Remote/EggInstallController.php @@ -10,23 +10,11 @@ class EggInstallController extends Controller { - /** - * @var \Pterodactyl\Services\Servers\EnvironmentService - */ - private $environment; - - /** - * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface - */ - private $repository; - /** * EggInstallController constructor. */ - public function __construct(EnvironmentService $environment, ServerRepositoryInterface $repository) + public function __construct(private EnvironmentService $environment, private ServerRepositoryInterface $repository) { - $this->environment = $environment; - $this->repository = $repository; } /** diff --git a/app/Http/Controllers/Api/Remote/Servers/ServerDetailsController.php b/app/Http/Controllers/Api/Remote/Servers/ServerDetailsController.php index 785544052b..8ce88e89ee 100644 --- a/app/Http/Controllers/Api/Remote/Servers/ServerDetailsController.php +++ b/app/Http/Controllers/Api/Remote/Servers/ServerDetailsController.php @@ -15,49 +15,24 @@ class ServerDetailsController extends Controller { - protected ConnectionInterface $connection; - - /** - * @var \Pterodactyl\Services\Eggs\EggConfigurationService - */ - private $eggConfigurationService; - - /** - * @var \Pterodactyl\Repositories\Eloquent\ServerRepository - */ - private $repository; - - /** - * @var \Pterodactyl\Services\Servers\ServerConfigurationStructureService - */ - private $configurationStructureService; - /** * ServerConfigurationController constructor. */ public function __construct( - ConnectionInterface $connection, - ServerRepository $repository, - ServerConfigurationStructureService $configurationStructureService, - EggConfigurationService $eggConfigurationService + protected ConnectionInterface $connection, + private ServerRepository $repository, + private ServerConfigurationStructureService $configurationStructureService, + private EggConfigurationService $eggConfigurationService ) { - $this->eggConfigurationService = $eggConfigurationService; - $this->repository = $repository; - $this->configurationStructureService = $configurationStructureService; - $this->connection = $connection; } /** * Returns details about the server that allows Wings to self-recover and ensure * that the state of the server matches the Panel at all times. * - * @param string $uuid - * - * @return \Illuminate\Http\JsonResponse - * * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function __invoke(Request $request, $uuid) + public function __invoke(Request $request, string $uuid): JsonResponse { $server = $this->repository->getByUuid($uuid); @@ -69,15 +44,13 @@ public function __invoke(Request $request, $uuid) /** * Lists all servers with their configurations that are assigned to the requesting node. - * - * @return \Pterodactyl\Http\Resources\Wings\ServerConfigurationCollection */ - public function list(Request $request) + public function list(Request $request): ServerConfigurationCollection { /** @var \Pterodactyl\Models\Node $node */ $node = $request->attributes->get('node'); - // Avoid run-away N+1 SQL queries by pre-loading the relationships that are used + // Avoid run-away N+1 SQL queries by preloading the relationships that are used // within each of the services called below. $servers = Server::query()->with('allocations', 'egg', 'mounts', 'variables', 'location') ->where('node_id', $node->id) @@ -94,15 +67,13 @@ public function list(Request $request) * do not get incorrectly stuck in installing/restoring from backup states since * a Wings reboot would completely stop those processes. * - * @return \Illuminate\Http\JsonResponse - * * @throws \Throwable */ - public function resetState(Request $request) + public function resetState(Request $request): JsonResponse { $node = $request->attributes->get('node'); - // Get all of the servers that are currently marked as restoring from a backup + // Get all the servers that are currently marked as restoring from a backup // on this node that do not have a failed backup tracked in the audit logs table // as well. // diff --git a/app/Http/Controllers/Api/Remote/Servers/ServerInstallController.php b/app/Http/Controllers/Api/Remote/Servers/ServerInstallController.php index 1c82e6d88a..e788437631 100644 --- a/app/Http/Controllers/Api/Remote/Servers/ServerInstallController.php +++ b/app/Http/Controllers/Api/Remote/Servers/ServerInstallController.php @@ -15,30 +15,16 @@ class ServerInstallController extends Controller { - /** - * @var \Pterodactyl\Repositories\Eloquent\ServerRepository - */ - private $repository; - - /** - * @var \Illuminate\Contracts\Events\Dispatcher - */ - private $eventDispatcher; - /** * ServerInstallController constructor. */ - public function __construct(ServerRepository $repository, EventDispatcher $eventDispatcher) + public function __construct(private ServerRepository $repository, private EventDispatcher $eventDispatcher) { - $this->repository = $repository; - $this->eventDispatcher = $eventDispatcher; } /** * Returns installation information for a server. * - * @return \Illuminate\Http\JsonResponse - * * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ public function index(Request $request, string $uuid): JsonResponse @@ -56,8 +42,6 @@ public function index(Request $request, string $uuid): JsonResponse /** * Updates the installation state of a server. * - * @return \Illuminate\Http\JsonResponse - * * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException * @throws \Pterodactyl\Exceptions\Model\DataValidationException */ @@ -77,7 +61,7 @@ public function store(InstallationDataRequest $request, string $uuid): JsonRespo $isInitialInstall = is_null($server->installed_at); if ($isInitialInstall && config()->get('pterodactyl.email.send_install_notification', true)) { $this->eventDispatcher->dispatch(new ServerInstalled($server)); - } elseif (! $isInitialInstall && config()->get('pterodactyl.email.send_reinstall_notification', true)) { + } elseif (!$isInitialInstall && config()->get('pterodactyl.email.send_reinstall_notification', true)) { $this->eventDispatcher->dispatch(new ServerInstalled($server)); } diff --git a/app/Http/Controllers/Api/Remote/Servers/ServerTransferController.php b/app/Http/Controllers/Api/Remote/Servers/ServerTransferController.php index 7e3ac810f4..6f49d66e01 100644 --- a/app/Http/Controllers/Api/Remote/Servers/ServerTransferController.php +++ b/app/Http/Controllers/Api/Remote/Servers/ServerTransferController.php @@ -16,68 +16,28 @@ use Pterodactyl\Repositories\Wings\DaemonServerRepository; use Pterodactyl\Repositories\Wings\DaemonTransferRepository; use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException; -use Pterodactyl\Services\Servers\ServerConfigurationStructureService; class ServerTransferController extends Controller { - /** - * @var \Illuminate\Database\ConnectionInterface - */ - private $connection; - - /** - * @var \Pterodactyl\Repositories\Eloquent\ServerRepository - */ - private $repository; - - /** - * @var \Pterodactyl\Repositories\Wings\DaemonServerRepository - */ - private $daemonServerRepository; - - /** - * @var \Pterodactyl\Repositories\Wings\DaemonTransferRepository - */ - private $daemonTransferRepository; - - /** - * @var \Pterodactyl\Services\Servers\ServerConfigurationStructureService - */ - private $configurationStructureService; - - /** - * @var \Pterodactyl\Services\Nodes\NodeJWTService - */ - private $jwtService; - /** * ServerTransferController constructor. */ public function __construct( - ConnectionInterface $connection, - ServerRepository $repository, - DaemonServerRepository $daemonServerRepository, - DaemonTransferRepository $daemonTransferRepository, - ServerConfigurationStructureService $configurationStructureService, - NodeJWTService $jwtService + private ConnectionInterface $connection, + private ServerRepository $repository, + private DaemonServerRepository $daemonServerRepository, + private DaemonTransferRepository $daemonTransferRepository, + private NodeJWTService $jwtService ) { - $this->connection = $connection; - $this->repository = $repository; - $this->daemonServerRepository = $daemonServerRepository; - $this->daemonTransferRepository = $daemonTransferRepository; - $this->configurationStructureService = $configurationStructureService; - $this->jwtService = $jwtService; } /** * The daemon notifies us about the archive status. * - * @return \Illuminate\Http\JsonResponse - * * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException * @throws \Throwable */ - public function archive(Request $request, string $uuid) + public function archive(Request $request, string $uuid): JsonResponse { $server = $this->repository->getByUuid($uuid); @@ -114,11 +74,9 @@ public function archive(Request $request, string $uuid) /** * The daemon notifies us about a transfer failure. * - * @return \Illuminate\Http\JsonResponse - * * @throws \Throwable */ - public function failure(string $uuid) + public function failure(string $uuid): JsonResponse { $server = $this->repository->getByUuid($uuid); @@ -168,14 +126,12 @@ public function success(string $uuid): JsonResponse } /** - * Release all of the reserved allocations for this transfer and mark it as failed in + * Release all the reserved allocations for this transfer and mark it as failed in * the database. * - * @return \Illuminate\Http\JsonResponse - * * @throws \Throwable */ - protected function processFailedTransfer(ServerTransfer $transfer) + protected function processFailedTransfer(ServerTransfer $transfer): JsonResponse { $this->connection->transaction(function () use (&$transfer) { $transfer->forceFill(['successful' => false])->saveOrFail(); diff --git a/app/Http/Controllers/Api/Remote/SftpAuthenticationController.php b/app/Http/Controllers/Api/Remote/SftpAuthenticationController.php index ab8ef8246e..a360d1d0ea 100644 --- a/app/Http/Controllers/Api/Remote/SftpAuthenticationController.php +++ b/app/Http/Controllers/Api/Remote/SftpAuthenticationController.php @@ -22,11 +22,8 @@ class SftpAuthenticationController extends Controller { use ThrottlesLogins; - protected GetUserPermissionsService $permissions; - - public function __construct(GetUserPermissionsService $permissions) + public function __construct(protected GetUserPermissionsService $permissions) { - $this->permissions = $permissions; } /** @@ -44,7 +41,7 @@ public function __invoke(SftpAuthenticationFormRequest $request): JsonResponse if ($this->hasTooManyLoginAttempts($request)) { $seconds = $this->limiter()->availableIn($this->throttleKey($request)); - throw new TooManyRequestsHttpException($seconds, "Too many login attempts for this account, please try again in {$seconds} seconds."); + throw new TooManyRequestsHttpException($seconds, "Too many login attempts for this account, please try again in $seconds seconds."); } $user = $this->getUser($request, $connection['username']); @@ -60,7 +57,7 @@ public function __invoke(SftpAuthenticationFormRequest $request): JsonResponse $key = null; try { $key = PublicKeyLoader::loadPublicKey(trim($request->input('password'))); - } catch (NoKeyLoadedException $exception) { + } catch (NoKeyLoadedException) { // do nothing } diff --git a/app/Http/Controllers/Auth/AbstractLoginController.php b/app/Http/Controllers/Auth/AbstractLoginController.php index 7c4b5c3942..f07282fbac 100644 --- a/app/Http/Controllers/Auth/AbstractLoginController.php +++ b/app/Http/Controllers/Auth/AbstractLoginController.php @@ -8,6 +8,8 @@ use Illuminate\Http\JsonResponse; use Illuminate\Auth\Events\Failed; use Illuminate\Container\Container; +use Illuminate\Support\Facades\Event; +use Pterodactyl\Events\Auth\DirectLogin; use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Http\Controllers\Controller; use Illuminate\Contracts\Auth\Authenticatable; @@ -21,24 +23,18 @@ abstract class AbstractLoginController extends Controller /** * Lockout time for failed login requests. - * - * @var int */ - protected $lockoutTime; + protected int $lockoutTime; /** * After how many attempts should logins be throttled and locked. - * - * @var int */ - protected $maxLoginAttempts; + protected int $maxLoginAttempts; /** * Where to redirect users after login / registration. - * - * @var string */ - protected $redirectTo = '/'; + protected string $redirectTo = '/'; /** * LoginController constructor. @@ -81,6 +77,8 @@ protected function sendLoginResponse(User $user, Request $request): JsonResponse $this->auth->guard()->login($user, true); + Event::dispatch(new DirectLogin($user, true)); + return new JsonResponse([ 'data' => [ 'complete' => true, @@ -91,7 +89,7 @@ protected function sendLoginResponse(User $user, Request $request): JsonResponse } /** - * Determine if the user is logging in using an email or username,. + * Determine if the user is logging in using an email or username. */ protected function getField(string $input = null): string { @@ -103,6 +101,6 @@ protected function getField(string $input = null): string */ protected function fireFailedLoginEvent(Authenticatable $user = null, array $credentials = []) { - event(new Failed('auth', $user, $credentials)); + Event::dispatch(new Failed('auth', $user, $credentials)); } } diff --git a/app/Http/Controllers/Auth/ForgotPasswordController.php b/app/Http/Controllers/Auth/ForgotPasswordController.php index 7b409e519d..d67b0de516 100644 --- a/app/Http/Controllers/Auth/ForgotPasswordController.php +++ b/app/Http/Controllers/Auth/ForgotPasswordController.php @@ -15,9 +15,6 @@ class ForgotPasswordController extends Controller /** * Get the response for a failed password reset link. - * - * @param \Illuminate\Http\Request - * @param string $response */ protected function sendResetLinkFailedResponse(Request $request, $response): JsonResponse { diff --git a/app/Http/Controllers/Auth/LoginCheckpointController.php b/app/Http/Controllers/Auth/LoginCheckpointController.php index d74b3f8a41..af05c55ef7 100644 --- a/app/Http/Controllers/Auth/LoginCheckpointController.php +++ b/app/Http/Controllers/Auth/LoginCheckpointController.php @@ -18,22 +18,15 @@ class LoginCheckpointController extends AbstractLoginController { private const TOKEN_EXPIRED_MESSAGE = 'The authentication token provided has expired, please refresh the page and try again.'; - private ValidationFactory $validation; - - private Google2FA $google2FA; - - private Encrypter $encrypter; - /** * LoginCheckpointController constructor. */ - public function __construct(Encrypter $encrypter, Google2FA $google2FA, ValidationFactory $validation) - { + public function __construct( + private Encrypter $encrypter, + private Google2FA $google2FA, + private ValidationFactory $validation + ) { parent::__construct(); - - $this->google2FA = $google2FA; - $this->encrypter = $encrypter; - $this->validation = $validation; } /** @@ -41,8 +34,6 @@ public function __construct(Encrypter $encrypter, Google2FA $google2FA, Validati * token. Once a user has reached this stage it is assumed that they have already * provided a valid username and password. * - * @return \Illuminate\Http\JsonResponse|void - * * @throws \PragmaRX\Google2FA\Exceptions\IncompatibleWithGoogleAuthenticatorException * @throws \PragmaRX\Google2FA\Exceptions\InvalidCharactersException * @throws \PragmaRX\Google2FA\Exceptions\SecretKeyTooShortException @@ -67,7 +58,7 @@ public function __invoke(LoginCheckpointRequest $request): JsonResponse try { /** @var \Pterodactyl\Models\User $user */ $user = User::query()->findOrFail($details['user_id']); - } catch (ModelNotFoundException $exception) { + } catch (ModelNotFoundException) { $this->sendFailedLoginResponse($request, null, self::TOKEN_EXPIRED_MESSAGE); } @@ -95,11 +86,9 @@ public function __invoke(LoginCheckpointRequest $request): JsonResponse * Determines if a given recovery token is valid for the user account. If we find a matching token * it will be deleted from the database. * - * @return bool - * * @throws \Exception */ - protected function isValidRecoveryToken(User $user, string $value) + protected function isValidRecoveryToken(User $user, string $value): bool { foreach ($user->recoveryTokens as $token) { if (password_verify($value, $token->token)) { diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php index 18cc8815c4..2dbb34ee36 100644 --- a/app/Http/Controllers/Auth/LoginController.php +++ b/app/Http/Controllers/Auth/LoginController.php @@ -14,22 +14,18 @@ class LoginController extends AbstractLoginController { - private ViewFactory $view; - /** * LoginController constructor. */ - public function __construct(ViewFactory $view) + public function __construct(private ViewFactory $view) { parent::__construct(); - - $this->view = $view; } /** * Handle all incoming requests for the authentication routes and render the - * base authentication view component. Vuejs will take over at this point and - * turn the login area into a SPA. + * base authentication view component. React will take over at this point and + * turn the login area into an SPA. */ public function index(): View { @@ -39,8 +35,6 @@ public function index(): View /** * Handle a login request to the application. * - * @return \Illuminate\Http\JsonResponse|void - * * @throws \Pterodactyl\Exceptions\DisplayException * @throws \Illuminate\Validation\ValidationException */ @@ -56,14 +50,14 @@ public function login(Request $request): JsonResponse /** @var \Pterodactyl\Models\User $user */ $user = User::query()->where($this->getField($username), $username)->firstOrFail(); - } catch (ModelNotFoundException $exception) { + } catch (ModelNotFoundException) { $this->sendFailedLoginResponse($request); } // Ensure that the account is using a valid username and password before trying to // continue. Previously this was handled in the 2FA checkpoint, however that has // a flaw in which you can discover if an account exists simply by seeing if you - // can proceede to the next step in the login process. + // can proceed to the next step in the login process. if (!password_verify($request->input('password'), $user->password)) { $this->sendFailedLoginResponse($request, $user); } diff --git a/app/Http/Controllers/Auth/ResetPasswordController.php b/app/Http/Controllers/Auth/ResetPasswordController.php index 1107510a84..3325a1e6b3 100644 --- a/app/Http/Controllers/Auth/ResetPasswordController.php +++ b/app/Http/Controllers/Auth/ResetPasswordController.php @@ -20,39 +20,19 @@ class ResetPasswordController extends Controller /** * The URL to redirect users to after password reset. - * - * @var string */ - public $redirectTo = '/'; + public string $redirectTo = '/'; - /** - * @var bool - */ - protected $hasTwoFactor = false; - - /** - * @var \Illuminate\Contracts\Events\Dispatcher - */ - private $dispatcher; - - /** - * @var \Illuminate\Contracts\Hashing\Hasher - */ - private $hasher; - - /** - * @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface - */ - private $userRepository; + protected bool $hasTwoFactor = false; /** * ResetPasswordController constructor. */ - public function __construct(Dispatcher $dispatcher, Hasher $hasher, UserRepositoryInterface $userRepository) - { - $this->dispatcher = $dispatcher; - $this->hasher = $hasher; - $this->userRepository = $userRepository; + public function __construct( + private Dispatcher $dispatcher, + private Hasher $hasher, + private UserRepositoryInterface $userRepository + ) { } /** @@ -64,7 +44,7 @@ public function __invoke(ResetPasswordRequest $request): JsonResponse { // Here we will attempt to reset the user's password. If it is successful we // will update the password on an actual user model and persist it to the - // database. Otherwise we will parse the error and return the response. + // database. Otherwise, we will parse the error and return the response. $response = $this->broker()->reset( $this->credentials($request), function ($user, $password) { diff --git a/app/Http/Controllers/Base/IndexController.php b/app/Http/Controllers/Base/IndexController.php index 058280be69..fecaa91a3e 100644 --- a/app/Http/Controllers/Base/IndexController.php +++ b/app/Http/Controllers/Base/IndexController.php @@ -2,31 +2,27 @@ namespace Pterodactyl\Http\Controllers\Base; +use Illuminate\View\View; +use Illuminate\View\Factory as ViewFactory; use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; class IndexController extends Controller { - /** - * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface - */ - protected $repository; - /** * IndexController constructor. */ - public function __construct(ServerRepositoryInterface $repository) - { - $this->repository = $repository; + public function __construct( + protected ServerRepositoryInterface $repository, + protected ViewFactory $view + ) { } /** * Returns listing of user's servers. - * - * @return \Illuminate\View\View */ - public function index() + public function index(): View { - return view('templates/base.core'); + return $this->view->make('templates/base.core'); } } diff --git a/app/Http/Controllers/Base/LocaleController.php b/app/Http/Controllers/Base/LocaleController.php index 8cd83b5ef1..5214defddd 100644 --- a/app/Http/Controllers/Base/LocaleController.php +++ b/app/Http/Controllers/Base/LocaleController.php @@ -19,10 +19,8 @@ public function __construct(Translator $translator) /** * Returns translation data given a specific locale and namespace. - * - * @return \Illuminate\Http\JsonResponse */ - public function __invoke(Request $request) + public function __invoke(Request $request): JsonResponse { $locales = explode(' ', $request->input('locale') ?? ''); $namespaces = explode(' ', $request->input('namespace') ?? ''); diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index f23d9f8308..7df1ed2b10 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -2,8 +2,8 @@ namespace Pterodactyl\Http; -use Fruitcake\Cors\HandleCors; use Illuminate\Auth\Middleware\Authorize; +use Illuminate\Http\Middleware\HandleCors; use Illuminate\Auth\Middleware\Authenticate; use Illuminate\Http\Middleware\TrustProxies; use Pterodactyl\Http\Middleware\TrimStrings; @@ -38,8 +38,6 @@ class Kernel extends HttpKernel { /** * The application's global HTTP middleware stack. - * - * @var array */ protected $middleware = [ TrustProxies::class, @@ -52,8 +50,6 @@ class Kernel extends HttpKernel /** * The application's route middleware groups. - * - * @var array */ protected $middlewareGroups = [ 'web' => [ @@ -89,8 +85,6 @@ class Kernel extends HttpKernel /** * The application's route middleware. - * - * @var array */ protected $routeMiddleware = [ 'auth' => Authenticate::class, diff --git a/app/Http/Middleware/Activity/TrackAPIKey.php b/app/Http/Middleware/Activity/TrackAPIKey.php index 90d53759ec..bf3394a239 100644 --- a/app/Http/Middleware/Activity/TrackAPIKey.php +++ b/app/Http/Middleware/Activity/TrackAPIKey.php @@ -14,10 +14,8 @@ class TrackAPIKey * API key, or it is just a cookie authenticated session. This data is set in a * request singleton so that all tracked activity log events are properly associated * with the given API key. - * - * @return mixed */ - public function handle(Request $request, Closure $next) + public function handle(Request $request, Closure $next): mixed { if ($request->user()) { $token = $request->user()->currentAccessToken(); diff --git a/app/Http/Middleware/Admin/Servers/ServerInstalled.php b/app/Http/Middleware/Admin/Servers/ServerInstalled.php index baf88af2de..63c90c3977 100644 --- a/app/Http/Middleware/Admin/Servers/ServerInstalled.php +++ b/app/Http/Middleware/Admin/Servers/ServerInstalled.php @@ -13,10 +13,8 @@ class ServerInstalled { /** * Checks that the server is installed before allowing access through the route. - * - * @return mixed */ - public function handle(Request $request, Closure $next) + public function handle(Request $request, Closure $next): mixed { /** @var \Pterodactyl\Models\Server|null $server */ $server = $request->route()->parameter('server'); diff --git a/app/Http/Middleware/AdminAuthenticate.php b/app/Http/Middleware/AdminAuthenticate.php index 72a7c86294..0bd0245067 100644 --- a/app/Http/Middleware/AdminAuthenticate.php +++ b/app/Http/Middleware/AdminAuthenticate.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Http\Middleware; @@ -18,11 +11,9 @@ class AdminAuthenticate /** * Handle an incoming request. * - * @return mixed - * * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException */ - public function handle(Request $request, Closure $next) + public function handle(Request $request, Closure $next): mixed { if (!$request->user() || !$request->user()->root_admin) { throw new AccessDeniedHttpException(); diff --git a/app/Http/Middleware/Api/Application/AuthenticateApplicationUser.php b/app/Http/Middleware/Api/Application/AuthenticateApplicationUser.php index 983f4d3696..2e366c4710 100644 --- a/app/Http/Middleware/Api/Application/AuthenticateApplicationUser.php +++ b/app/Http/Middleware/Api/Application/AuthenticateApplicationUser.php @@ -11,10 +11,8 @@ class AuthenticateApplicationUser /** * Authenticate that the currently authenticated user is an administrator * and should be allowed to proceed through the application API. - * - * @return mixed */ - public function handle(Request $request, Closure $next) + public function handle(Request $request, Closure $next): mixed { /** @var \Pterodactyl\Models\User|null $user */ $user = $request->user(); diff --git a/app/Http/Middleware/Api/AuthenticateIPAccess.php b/app/Http/Middleware/Api/AuthenticateIPAccess.php index 163d03d68c..c55ce8b3f2 100644 --- a/app/Http/Middleware/Api/AuthenticateIPAccess.php +++ b/app/Http/Middleware/Api/AuthenticateIPAccess.php @@ -15,12 +15,10 @@ class AuthenticateIPAccess /** * Determine if a request IP has permission to access the API. * - * @return mixed - * * @throws \Exception * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException */ - public function handle(Request $request, Closure $next) + public function handle(Request $request, Closure $next): mixed { /** @var \Laravel\Sanctum\TransientToken|\Pterodactyl\Models\ApiKey $token */ $token = $request->user()->currentAccessToken(); diff --git a/app/Http/Middleware/Api/Client/RequireClientApiKey.php b/app/Http/Middleware/Api/Client/RequireClientApiKey.php index 7203203c7f..1ddd0f128a 100644 --- a/app/Http/Middleware/Api/Client/RequireClientApiKey.php +++ b/app/Http/Middleware/Api/Client/RequireClientApiKey.php @@ -11,10 +11,8 @@ class RequireClientApiKey /** * Blocks a request to the Client API endpoints if the user is providing an API token * that was created for the application API. - * - * @return mixed */ - public function handle(Request $request, \Closure $next) + public function handle(Request $request, \Closure $next): mixed { $token = $request->user()->currentAccessToken(); diff --git a/app/Http/Middleware/Api/Client/Server/AuthenticateServerAccess.php b/app/Http/Middleware/Api/Client/Server/AuthenticateServerAccess.php index 0634e25858..40a4d0cf1c 100644 --- a/app/Http/Middleware/Api/Client/Server/AuthenticateServerAccess.php +++ b/app/Http/Middleware/Api/Client/Server/AuthenticateServerAccess.php @@ -5,40 +5,29 @@ use Closure; use Illuminate\Http\Request; use Pterodactyl\Models\Server; -use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Pterodactyl\Exceptions\Http\Server\ServerStateConflictException; class AuthenticateServerAccess { - /** - * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface - */ - private $repository; - /** * Routes that this middleware should not apply to if the user is an admin. - * - * @var string[] */ - protected $except = [ + protected array $except = [ 'api:client:server.ws', ]; /** * AuthenticateServerAccess constructor. */ - public function __construct(ServerRepositoryInterface $repository) + public function __construct() { - $this->repository = $repository; } /** * Authenticate that this server exists and is not suspended or marked as installing. - * - * @return mixed */ - public function handle(Request $request, Closure $next) + public function handle(Request $request, Closure $next): mixed { /** @var \Pterodactyl\Models\User $user */ $user = $request->user(); diff --git a/app/Http/Middleware/Api/Client/Server/ResourceBelongsToServer.php b/app/Http/Middleware/Api/Client/Server/ResourceBelongsToServer.php index d8c4731d3d..5d3530d865 100644 --- a/app/Http/Middleware/Api/Client/Server/ResourceBelongsToServer.php +++ b/app/Http/Middleware/Api/Client/Server/ResourceBelongsToServer.php @@ -25,10 +25,8 @@ class ResourceBelongsToServer * This is critical to ensuring that all subsequent logic is using exactly the * server that is expected, and that we're not accessing a resource completely * unrelated to the server provided in the request. - * - * @return mixed */ - public function handle(Request $request, Closure $next) + public function handle(Request $request, Closure $next): mixed { $params = $request->route()->parameters(); if (is_null($params) || !$params['server'] instanceof Server) { @@ -59,8 +57,8 @@ public function handle(Request $request, Closure $next) throw $exception; } break; - // Regular users are a special case here as we need to make sure they're - // currently assigned as a subuser on the server. + // Regular users are a special case here as we need to make sure they're + // currently assigned as a subuser on the server. case User::class: $subuser = $server->subusers()->where('user_id', $model->id)->first(); if (is_null($subuser)) { @@ -70,8 +68,8 @@ public function handle(Request $request, Closure $next) // in the underlying logic. $request->attributes->set('subuser', $subuser); break; - // Tasks are special since they're (currently) the only item in the API - // that requires something in addition to the server in order to be accessed. + // Tasks are special since they're (currently) the only item in the API + // that requires something in addition to the server in order to be accessed. case Task::class: $schedule = $request->route()->parameter('schedule'); if ($model->schedule_id !== $schedule->id || $schedule->server_id !== $server->id) { diff --git a/app/Http/Middleware/Api/Client/SubstituteClientBindings.php b/app/Http/Middleware/Api/Client/SubstituteClientBindings.php index 38d87bf2f4..ec30d80fd7 100644 --- a/app/Http/Middleware/Api/Client/SubstituteClientBindings.php +++ b/app/Http/Middleware/Api/Client/SubstituteClientBindings.php @@ -10,10 +10,8 @@ class SubstituteClientBindings extends SubstituteBindings { /** * @param \Illuminate\Http\Request $request - * - * @return mixed */ - public function handle($request, Closure $next) + public function handle($request, Closure $next): mixed { // Override default behavior of the model binding to use a specific table // column rather than the default 'id'. diff --git a/app/Http/Middleware/Api/Daemon/DaemonAuthenticate.php b/app/Http/Middleware/Api/Daemon/DaemonAuthenticate.php index f79d598ebc..cea8bc5ed6 100644 --- a/app/Http/Middleware/Api/Daemon/DaemonAuthenticate.php +++ b/app/Http/Middleware/Api/Daemon/DaemonAuthenticate.php @@ -13,42 +13,26 @@ class DaemonAuthenticate { - /** - * @var \Pterodactyl\Repositories\Eloquent\NodeRepository - */ - private $repository; - - /** - * @var \Illuminate\Contracts\Encryption\Encrypter - */ - private $encrypter; - /** * Daemon routes that this middleware should be skipped on. - * - * @var array */ - protected $except = [ + protected array $except = [ 'daemon.configuration', ]; /** * DaemonAuthenticate constructor. */ - public function __construct(Encrypter $encrypter, NodeRepository $repository) + public function __construct(private Encrypter $encrypter, private NodeRepository $repository) { - $this->repository = $repository; - $this->encrypter = $encrypter; } /** * Check if a request from the daemon can be properly attributed back to a single node instance. * - * @return mixed - * * @throws \Symfony\Component\HttpKernel\Exception\HttpException */ - public function handle(Request $request, Closure $next) + public function handle(Request $request, Closure $next): mixed { if (in_array($request->route()->getName(), $this->except)) { return $next($request); diff --git a/app/Http/Middleware/Api/IsValidJson.php b/app/Http/Middleware/Api/IsValidJson.php index 5f53b097d6..e35173aac6 100644 --- a/app/Http/Middleware/Api/IsValidJson.php +++ b/app/Http/Middleware/Api/IsValidJson.php @@ -13,10 +13,8 @@ class IsValidJson * Throw an exception if the request should be valid JSON data but there is an error while * parsing the data. This avoids confusing validation errors where every field is flagged and * it is not immediately clear that there is an issue with the JSON being passed. - * - * @return mixed */ - public function handle(Request $request, Closure $next) + public function handle(Request $request, Closure $next): mixed { if ($request->isJson() && !empty($request->getContent())) { try { diff --git a/app/Http/Middleware/EncryptCookies.php b/app/Http/Middleware/EncryptCookies.php index 9c0cadd86d..1ac425a33d 100644 --- a/app/Http/Middleware/EncryptCookies.php +++ b/app/Http/Middleware/EncryptCookies.php @@ -8,8 +8,6 @@ class EncryptCookies extends BaseEncrypter { /** * The names of the cookies that should not be encrypted. - * - * @var array */ protected $except = []; } diff --git a/app/Http/Middleware/LanguageMiddleware.php b/app/Http/Middleware/LanguageMiddleware.php index 914d4395f8..2f0d53b47d 100644 --- a/app/Http/Middleware/LanguageMiddleware.php +++ b/app/Http/Middleware/LanguageMiddleware.php @@ -8,25 +8,17 @@ class LanguageMiddleware { - /** - * @var \Illuminate\Foundation\Application - */ - private $app; - /** * LanguageMiddleware constructor. */ - public function __construct(Application $app) + public function __construct(private Application $app) { - $this->app = $app; } /** * Handle an incoming request and set the user's preferred language. - * - * @return mixed */ - public function handle(Request $request, Closure $next) + public function handle(Request $request, Closure $next): mixed { $this->app->setLocale($request->user()->language ?? config('app.locale', 'en')); diff --git a/app/Http/Middleware/MaintenanceMiddleware.php b/app/Http/Middleware/MaintenanceMiddleware.php index 5826261622..9899542d17 100644 --- a/app/Http/Middleware/MaintenanceMiddleware.php +++ b/app/Http/Middleware/MaintenanceMiddleware.php @@ -3,31 +3,22 @@ namespace Pterodactyl\Http\Middleware; use Closure; +use Illuminate\Http\Request; use Illuminate\Contracts\Routing\ResponseFactory; class MaintenanceMiddleware { - /** - * @var \Illuminate\Contracts\Routing\ResponseFactory - */ - private $response; - /** * MaintenanceMiddleware constructor. */ - public function __construct(ResponseFactory $response) + public function __construct(private ResponseFactory $response) { - $this->response = $response; } /** * Handle an incoming request. - * - * @param \Illuminate\Http\Request $request - * - * @return mixed */ - public function handle($request, Closure $next) + public function handle(Request $request, Closure $next): mixed { /** @var \Pterodactyl\Models\Server $server */ $server = $request->attributes->get('server'); diff --git a/app/Http/Middleware/RedirectIfAuthenticated.php b/app/Http/Middleware/RedirectIfAuthenticated.php index 6c4019af46..c1efd927da 100644 --- a/app/Http/Middleware/RedirectIfAuthenticated.php +++ b/app/Http/Middleware/RedirectIfAuthenticated.php @@ -8,25 +8,17 @@ class RedirectIfAuthenticated { - /** - * @var \Illuminate\Auth\AuthManager - */ - private $authManager; - /** * RedirectIfAuthenticated constructor. */ - public function __construct(AuthManager $authManager) + public function __construct(private AuthManager $authManager) { - $this->authManager = $authManager; } /** * Handle an incoming request. - * - * @return mixed */ - public function handle(Request $request, Closure $next, string $guard = null) + public function handle(Request $request, Closure $next, string $guard = null): mixed { if ($this->authManager->guard($guard)->check()) { return redirect()->route('index'); diff --git a/app/Http/Middleware/RequireTwoFactorAuthentication.php b/app/Http/Middleware/RequireTwoFactorAuthentication.php index 6691179a34..e3307727f2 100644 --- a/app/Http/Middleware/RequireTwoFactorAuthentication.php +++ b/app/Http/Middleware/RequireTwoFactorAuthentication.php @@ -15,23 +15,15 @@ class RequireTwoFactorAuthentication public const LEVEL_ALL = 2; /** - * @var \Prologue\Alerts\AlertsMessageBag + * The route to redirect a user to enable 2FA. */ - private $alert; - - /** - * The route to redirect a user to to enable 2FA. - * - * @var string - */ - protected $redirectRoute = '/account'; + protected string $redirectRoute = '/account'; /** * RequireTwoFactorAuthentication constructor. */ - public function __construct(AlertsMessageBag $alert) + public function __construct(private AlertsMessageBag $alert) { - $this->alert = $alert; } /** @@ -40,11 +32,9 @@ public function __construct(AlertsMessageBag $alert) * order to perform actions. If so, we check the level at which it is required (all users * or just admins) and then check if the user has enabled it for their account. * - * @return mixed - * * @throws \Pterodactyl\Exceptions\Http\TwoFactorAuthRequiredException */ - public function handle(Request $request, Closure $next) + public function handle(Request $request, Closure $next): mixed { /** @var \Pterodactyl\Models\User $user */ $user = $request->user(); diff --git a/app/Http/Middleware/TrimStrings.php b/app/Http/Middleware/TrimStrings.php index 04f434b982..af5382e8c9 100644 --- a/app/Http/Middleware/TrimStrings.php +++ b/app/Http/Middleware/TrimStrings.php @@ -8,8 +8,6 @@ class TrimStrings extends BaseTrimmer { /** * The names of the attributes that should not be trimmed. - * - * @var array */ protected $except = [ 'password', diff --git a/app/Http/Middleware/VerifyCsrfToken.php b/app/Http/Middleware/VerifyCsrfToken.php index ee5a3d216d..d951fe77b1 100644 --- a/app/Http/Middleware/VerifyCsrfToken.php +++ b/app/Http/Middleware/VerifyCsrfToken.php @@ -10,8 +10,6 @@ class VerifyCsrfToken extends BaseVerifier * The URIs that should be excluded from CSRF verification. These are * never hit by the front-end, and require specific token validation * to work. - * - * @var string[] */ protected $except = ['remote/*', 'daemon/*']; } diff --git a/app/Http/Middleware/VerifyReCaptcha.php b/app/Http/Middleware/VerifyReCaptcha.php index c0841f2c98..45ae4bb23f 100644 --- a/app/Http/Middleware/VerifyReCaptcha.php +++ b/app/Http/Middleware/VerifyReCaptcha.php @@ -14,33 +14,17 @@ class VerifyReCaptcha { - /** - * @var \Illuminate\Contracts\Config\Repository - */ - private $config; - - /** - * @var \Illuminate\Contracts\Events\Dispatcher - */ - private $dispatcher; - /** * VerifyReCaptcha constructor. */ - public function __construct(Dispatcher $dispatcher, Repository $config) + public function __construct(private Dispatcher $dispatcher, private Repository $config) { - $this->config = $config; - $this->dispatcher = $dispatcher; } /** * Handle an incoming request. - * - * @param \Illuminate\Http\Request $request - * - * @return \Illuminate\Http\RedirectResponse|mixed */ - public function handle($request, Closure $next) + public function handle(Request $request, Closure $next): mixed { if (!$this->config->get('recaptcha.enabled')) { return $next($request); diff --git a/app/Http/Requests/Admin/AdminFormRequest.php b/app/Http/Requests/Admin/AdminFormRequest.php index e3107e26da..8597e1d939 100644 --- a/app/Http/Requests/Admin/AdminFormRequest.php +++ b/app/Http/Requests/Admin/AdminFormRequest.php @@ -8,18 +8,14 @@ abstract class AdminFormRequest extends FormRequest { /** * The rules to apply to the incoming form request. - * - * @return array */ - abstract public function rules(); + abstract public function rules(): array; /** * Determine if the user is an admin and has permission to access this * form controller in the first place. - * - * @return bool */ - public function authorize() + public function authorize(): bool { if (is_null($this->user())) { return false; @@ -31,10 +27,8 @@ public function authorize() /** * Return only the fields that we are interested in from the request. * This will include empty fields as a null value. - * - * @return array */ - public function normalize(array $only = null) + public function normalize(array $only = null): array { return $this->only($only ?? array_keys($this->rules())); } diff --git a/app/Http/Requests/Admin/Api/StoreApplicationApiKeyRequest.php b/app/Http/Requests/Admin/Api/StoreApplicationApiKeyRequest.php index 40aa732f2c..f451239e67 100644 --- a/app/Http/Requests/Admin/Api/StoreApplicationApiKeyRequest.php +++ b/app/Http/Requests/Admin/Api/StoreApplicationApiKeyRequest.php @@ -9,12 +9,10 @@ class StoreApplicationApiKeyRequest extends AdminFormRequest { /** - * @return array - * * @throws \ReflectionException * @throws \ReflectionException */ - public function rules() + public function rules(): array { $modelRules = ApiKey::getRules(); @@ -23,10 +21,7 @@ public function rules() })->merge(['memo' => $modelRules['memo']])->toArray(); } - /** - * @return array - */ - public function attributes() + public function attributes(): array { return [ 'memo' => 'Description', diff --git a/app/Http/Requests/Admin/BaseFormRequest.php b/app/Http/Requests/Admin/BaseFormRequest.php index dff6b9fb2f..cd2c78e4dc 100644 --- a/app/Http/Requests/Admin/BaseFormRequest.php +++ b/app/Http/Requests/Admin/BaseFormRequest.php @@ -1,17 +1,10 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Http\Requests\Admin; class BaseFormRequest extends AdminFormRequest { - public function rules() + public function rules(): array { return [ 'company' => 'required|between:1,256', diff --git a/app/Http/Requests/Admin/DatabaseHostFormRequest.php b/app/Http/Requests/Admin/DatabaseHostFormRequest.php index 7fdd0cdc32..2e581478ec 100644 --- a/app/Http/Requests/Admin/DatabaseHostFormRequest.php +++ b/app/Http/Requests/Admin/DatabaseHostFormRequest.php @@ -3,13 +3,11 @@ namespace Pterodactyl\Http\Requests\Admin; use Pterodactyl\Models\DatabaseHost; +use Illuminate\Contracts\Validation\Validator; class DatabaseHostFormRequest extends AdminFormRequest { - /** - * @return mixed - */ - public function rules() + public function rules(): array { if ($this->method() !== 'POST') { return DatabaseHost::getRulesForUpdate($this->route()->parameter('host')); @@ -20,10 +18,8 @@ public function rules() /** * Modify submitted data before it is passed off to the validator. - * - * @return \Illuminate\Contracts\Validation\Validator */ - protected function getValidatorInstance() + protected function getValidatorInstance(): Validator { if (!$this->filled('node_id')) { $this->merge(['node_id' => null]); diff --git a/app/Http/Requests/Admin/Egg/EggFormRequest.php b/app/Http/Requests/Admin/Egg/EggFormRequest.php index 2dbc01b512..0f153393fb 100644 --- a/app/Http/Requests/Admin/Egg/EggFormRequest.php +++ b/app/Http/Requests/Admin/Egg/EggFormRequest.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Http\Requests\Admin\Egg; @@ -13,10 +6,7 @@ class EggFormRequest extends AdminFormRequest { - /** - * {@inheritdoc} - */ - public function rules() + public function rules(): array { $rules = [ 'name' => 'required|string|max:191', @@ -39,9 +29,6 @@ public function rules() return $rules; } - /** - * @param \Illuminate\Contracts\Validation\Validator $validator - */ public function withValidator($validator) { $validator->sometimes('config_from', 'exists:eggs,id', function () { @@ -49,7 +36,7 @@ public function withValidator($validator) }); } - public function validated(): array + public function validated($key = null, $default = null): array { $data = parent::validated(); diff --git a/app/Http/Requests/Admin/Egg/EggImportFormRequest.php b/app/Http/Requests/Admin/Egg/EggImportFormRequest.php index b6adb768eb..7718480e93 100644 --- a/app/Http/Requests/Admin/Egg/EggImportFormRequest.php +++ b/app/Http/Requests/Admin/Egg/EggImportFormRequest.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Http\Requests\Admin\Egg; @@ -13,10 +6,7 @@ class EggImportFormRequest extends AdminFormRequest { - /** - * @return array - */ - public function rules() + public function rules(): array { $rules = [ 'import_file' => 'bail|required|file|max:1000|mimetypes:application/json,text/plain', diff --git a/app/Http/Requests/Admin/Egg/EggScriptFormRequest.php b/app/Http/Requests/Admin/Egg/EggScriptFormRequest.php index 3f522e96fa..b93a63c63c 100644 --- a/app/Http/Requests/Admin/Egg/EggScriptFormRequest.php +++ b/app/Http/Requests/Admin/Egg/EggScriptFormRequest.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Http\Requests\Admin\Egg; @@ -14,11 +7,9 @@ class EggScriptFormRequest extends AdminFormRequest { /** - * Return the rules to be used when validating the sent data in the request. - * - * @return array + * Return the rules to be used when validating the data sent in the request. */ - public function rules() + public function rules(): array { return [ 'script_install' => 'sometimes|nullable|string', diff --git a/app/Http/Requests/Admin/Egg/EggVariableFormRequest.php b/app/Http/Requests/Admin/Egg/EggVariableFormRequest.php index d52fe94d2c..d232254eab 100644 --- a/app/Http/Requests/Admin/Egg/EggVariableFormRequest.php +++ b/app/Http/Requests/Admin/Egg/EggVariableFormRequest.php @@ -9,10 +9,8 @@ class EggVariableFormRequest extends AdminFormRequest { /** * Define rules for validation of this request. - * - * @return array */ - public function rules() + public function rules(): array { return [ 'name' => 'required|string|min:1|max:191', diff --git a/app/Http/Requests/Admin/LocationFormRequest.php b/app/Http/Requests/Admin/LocationFormRequest.php index 2ad202f9c3..b10e304a07 100644 --- a/app/Http/Requests/Admin/LocationFormRequest.php +++ b/app/Http/Requests/Admin/LocationFormRequest.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Http\Requests\Admin; @@ -14,11 +7,9 @@ class LocationFormRequest extends AdminFormRequest { /** - * Setup the validation rules to use for these requests. - * - * @return array + * Set up the validation rules to use for these requests. */ - public function rules() + public function rules(): array { if ($this->method() === 'PATCH') { return Location::getRulesForUpdate($this->route()->parameter('location')->id); diff --git a/app/Http/Requests/Admin/MountFormRequest.php b/app/Http/Requests/Admin/MountFormRequest.php index bd94a633a5..074ea4a503 100644 --- a/app/Http/Requests/Admin/MountFormRequest.php +++ b/app/Http/Requests/Admin/MountFormRequest.php @@ -7,11 +7,9 @@ class MountFormRequest extends AdminFormRequest { /** - * Setup the validation rules to use for these requests. - * - * @return array + * Set up the validation rules to use for these requests. */ - public function rules() + public function rules(): array { if ($this->method() === 'PATCH') { return Mount::getRulesForUpdate($this->route()->parameter('mount')->id); diff --git a/app/Http/Requests/Admin/Nest/StoreNestFormRequest.php b/app/Http/Requests/Admin/Nest/StoreNestFormRequest.php index 2f01dfe9e6..193f8676c8 100644 --- a/app/Http/Requests/Admin/Nest/StoreNestFormRequest.php +++ b/app/Http/Requests/Admin/Nest/StoreNestFormRequest.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Http\Requests\Admin\Nest; @@ -13,10 +6,7 @@ class StoreNestFormRequest extends AdminFormRequest { - /** - * @return array - */ - public function rules() + public function rules(): array { return [ 'name' => 'required|string|min:1|max:191', diff --git a/app/Http/Requests/Admin/Node/AllocationAliasFormRequest.php b/app/Http/Requests/Admin/Node/AllocationAliasFormRequest.php index 2552114abf..f388203a05 100644 --- a/app/Http/Requests/Admin/Node/AllocationAliasFormRequest.php +++ b/app/Http/Requests/Admin/Node/AllocationAliasFormRequest.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Http\Requests\Admin\Node; @@ -13,10 +6,7 @@ class AllocationAliasFormRequest extends AdminFormRequest { - /** - * @return array - */ - public function rules() + public function rules(): array { return [ 'alias' => 'present|nullable|string', diff --git a/app/Http/Requests/Admin/Node/AllocationFormRequest.php b/app/Http/Requests/Admin/Node/AllocationFormRequest.php index 3c580c0264..641b8d08a1 100644 --- a/app/Http/Requests/Admin/Node/AllocationFormRequest.php +++ b/app/Http/Requests/Admin/Node/AllocationFormRequest.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Http\Requests\Admin\Node; @@ -13,10 +6,7 @@ class AllocationFormRequest extends AdminFormRequest { - /** - * @return array - */ - public function rules() + public function rules(): array { return [ 'allocation_ip' => 'required|string', diff --git a/app/Http/Requests/Admin/Node/NodeFormRequest.php b/app/Http/Requests/Admin/Node/NodeFormRequest.php index c4a294cad0..f95189f295 100644 --- a/app/Http/Requests/Admin/Node/NodeFormRequest.php +++ b/app/Http/Requests/Admin/Node/NodeFormRequest.php @@ -11,7 +11,7 @@ class NodeFormRequest extends AdminFormRequest /** * Get rules to apply to data in this request. */ - public function rules() + public function rules(): array { if ($this->method() === 'PATCH') { return Node::getRulesForUpdate($this->route()->parameter('node')); diff --git a/app/Http/Requests/Admin/ServerFormRequest.php b/app/Http/Requests/Admin/ServerFormRequest.php index e20210b355..bf461dc956 100644 --- a/app/Http/Requests/Admin/ServerFormRequest.php +++ b/app/Http/Requests/Admin/ServerFormRequest.php @@ -4,15 +4,14 @@ use Pterodactyl\Models\Server; use Illuminate\Validation\Rule; +use Illuminate\Validation\Validator; class ServerFormRequest extends AdminFormRequest { /** * Rules to be applied to this request. - * - * @return array */ - public function rules() + public function rules(): array { $rules = Server::getRules(); $rules['description'][] = 'nullable'; @@ -23,14 +22,12 @@ public function rules() /** * Run validation after the rules above have been applied. - * - * @param \Illuminate\Validation\Validator $validator */ - public function withValidator($validator) + public function withValidator(Validator $validator): void { $validator->after(function ($validator) { $validator->sometimes('node_id', 'required|numeric|bail|exists:nodes,id', function ($input) { - return !($input->auto_deploy); + return !$input->auto_deploy; }); $validator->sometimes('allocation_id', [ @@ -42,7 +39,7 @@ public function withValidator($validator) $query->whereNull('server_id'); }), ], function ($input) { - return !($input->auto_deploy); + return !$input->auto_deploy; }); $validator->sometimes('allocation_additional.*', [ @@ -54,7 +51,7 @@ public function withValidator($validator) $query->whereNull('server_id'); }), ], function ($input) { - return !($input->auto_deploy); + return !$input->auto_deploy; }); }); } diff --git a/app/Http/Requests/Admin/Settings/AdvancedSettingsFormRequest.php b/app/Http/Requests/Admin/Settings/AdvancedSettingsFormRequest.php index c53825ffe7..17608d9f27 100644 --- a/app/Http/Requests/Admin/Settings/AdvancedSettingsFormRequest.php +++ b/app/Http/Requests/Admin/Settings/AdvancedSettingsFormRequest.php @@ -7,11 +7,9 @@ class AdvancedSettingsFormRequest extends AdminFormRequest { /** - * Return all of the rules to apply to this request's data. - * - * @return array + * Return all the rules to apply to this request's data. */ - public function rules() + public function rules(): array { return [ 'recaptcha:enabled' => 'required|in:true,false', @@ -36,10 +34,7 @@ public function rules() ]; } - /** - * @return array - */ - public function attributes() + public function attributes(): array { return [ 'recaptcha:enabled' => 'reCAPTCHA Enabled', diff --git a/app/Http/Requests/Admin/Settings/BaseSettingsFormRequest.php b/app/Http/Requests/Admin/Settings/BaseSettingsFormRequest.php index 05d0f9d486..8a24dd0ac8 100644 --- a/app/Http/Requests/Admin/Settings/BaseSettingsFormRequest.php +++ b/app/Http/Requests/Admin/Settings/BaseSettingsFormRequest.php @@ -10,10 +10,7 @@ class BaseSettingsFormRequest extends AdminFormRequest { use AvailableLanguages; - /** - * @return array - */ - public function rules() + public function rules(): array { return [ 'app:name' => 'required|string|max:191', @@ -22,10 +19,7 @@ public function rules() ]; } - /** - * @return array - */ - public function attributes() + public function attributes(): array { return [ 'app:name' => 'Company Name', diff --git a/app/Http/Requests/Admin/Settings/MailSettingsFormRequest.php b/app/Http/Requests/Admin/Settings/MailSettingsFormRequest.php index 0ad24181ab..4475b08fd8 100644 --- a/app/Http/Requests/Admin/Settings/MailSettingsFormRequest.php +++ b/app/Http/Requests/Admin/Settings/MailSettingsFormRequest.php @@ -9,10 +9,8 @@ class MailSettingsFormRequest extends AdminFormRequest { /** * Return rules to validate mail settings POST data against. - * - * @return array */ - public function rules() + public function rules(): array { return [ 'mail:host' => 'required|string', @@ -28,12 +26,8 @@ public function rules() /** * Override the default normalization function for this type of request * as we need to accept empty values on the keys. - * - * @param array $only - * - * @return array */ - public function normalize(array $only = null) + public function normalize(array $only = null): array { $keys = array_flip(array_keys($this->rules())); diff --git a/app/Http/Requests/Admin/UserFormRequest.php b/app/Http/Requests/Admin/UserFormRequest.php index 4203e65d9c..ae5b5f346f 100644 --- a/app/Http/Requests/Admin/UserFormRequest.php +++ b/app/Http/Requests/Admin/UserFormRequest.php @@ -11,7 +11,7 @@ class UserFormRequest extends AdminFormRequest * Rules to apply to requests for updating or creating a user * in the Admin CP. */ - public function rules() + public function rules(): array { return Collection::make( User::getRulesForUpdate($this->route()->parameter('user')) diff --git a/app/Http/Requests/Api/Application/Allocations/DeleteAllocationRequest.php b/app/Http/Requests/Api/Application/Allocations/DeleteAllocationRequest.php index 9005348589..6529a9a5af 100644 --- a/app/Http/Requests/Api/Application/Allocations/DeleteAllocationRequest.php +++ b/app/Http/Requests/Api/Application/Allocations/DeleteAllocationRequest.php @@ -7,13 +7,7 @@ class DeleteAllocationRequest extends ApplicationApiRequest { - /** - * @var string - */ - protected $resource = AdminAcl::RESOURCE_ALLOCATIONS; + protected ?string $resource = AdminAcl::RESOURCE_ALLOCATIONS; - /** - * @var int - */ - protected $permission = AdminAcl::WRITE; + protected int $permission = AdminAcl::WRITE; } diff --git a/app/Http/Requests/Api/Application/Allocations/GetAllocationsRequest.php b/app/Http/Requests/Api/Application/Allocations/GetAllocationsRequest.php index 50afcf9600..f03223f2d2 100644 --- a/app/Http/Requests/Api/Application/Allocations/GetAllocationsRequest.php +++ b/app/Http/Requests/Api/Application/Allocations/GetAllocationsRequest.php @@ -7,13 +7,7 @@ class GetAllocationsRequest extends ApplicationApiRequest { - /** - * @var string - */ - protected $resource = AdminAcl::RESOURCE_ALLOCATIONS; + protected ?string $resource = AdminAcl::RESOURCE_ALLOCATIONS; - /** - * @var int - */ - protected $permission = AdminAcl::READ; + protected int $permission = AdminAcl::READ; } diff --git a/app/Http/Requests/Api/Application/Allocations/StoreAllocationRequest.php b/app/Http/Requests/Api/Application/Allocations/StoreAllocationRequest.php index d1f5415a6a..a7e0c4da2d 100644 --- a/app/Http/Requests/Api/Application/Allocations/StoreAllocationRequest.php +++ b/app/Http/Requests/Api/Application/Allocations/StoreAllocationRequest.php @@ -7,15 +7,9 @@ class StoreAllocationRequest extends ApplicationApiRequest { - /** - * @var string - */ - protected $resource = AdminAcl::RESOURCE_ALLOCATIONS; + protected ?string $resource = AdminAcl::RESOURCE_ALLOCATIONS; - /** - * @var int - */ - protected $permission = AdminAcl::WRITE; + protected int $permission = AdminAcl::WRITE; public function rules(): array { @@ -27,10 +21,7 @@ public function rules(): array ]; } - /** - * @return array - */ - public function validated() + public function validated($key = null, $default = null): array { $data = parent::validated(); diff --git a/app/Http/Requests/Api/Application/ApplicationApiRequest.php b/app/Http/Requests/Api/Application/ApplicationApiRequest.php index f3960b3060..2e0ed133a5 100644 --- a/app/Http/Requests/Api/Application/ApplicationApiRequest.php +++ b/app/Http/Requests/Api/Application/ApplicationApiRequest.php @@ -16,18 +16,14 @@ abstract class ApplicationApiRequest extends FormRequest /** * The resource that should be checked when performing the authorization * function for this request. - * - * @var string|null */ - protected $resource; + protected ?string $resource; /** * The permission level that a given API key should have for accessing * the defined $resource during the request cycle. - * - * @var int */ - protected $permission = AdminAcl::NONE; + protected int $permission = AdminAcl::NONE; /** * Determine if the current user is authorized to perform @@ -80,7 +76,7 @@ public function withValidator(Validator $validator): void * @param class-string $expect * * @return T - * @noinspection PhpUndefinedClassInspection + * * @noinspection PhpDocSignatureInspection */ public function parameter(string $key, string $expect) diff --git a/app/Http/Requests/Api/Application/Locations/DeleteLocationRequest.php b/app/Http/Requests/Api/Application/Locations/DeleteLocationRequest.php index 880a58d1aa..3c41e11663 100644 --- a/app/Http/Requests/Api/Application/Locations/DeleteLocationRequest.php +++ b/app/Http/Requests/Api/Application/Locations/DeleteLocationRequest.php @@ -7,13 +7,7 @@ class DeleteLocationRequest extends ApplicationApiRequest { - /** - * @var string - */ - protected $resource = AdminAcl::RESOURCE_LOCATIONS; + protected ?string $resource = AdminAcl::RESOURCE_LOCATIONS; - /** - * @var int - */ - protected $permission = AdminAcl::WRITE; + protected int $permission = AdminAcl::WRITE; } diff --git a/app/Http/Requests/Api/Application/Locations/GetLocationsRequest.php b/app/Http/Requests/Api/Application/Locations/GetLocationsRequest.php index 5edf004629..65157dc475 100644 --- a/app/Http/Requests/Api/Application/Locations/GetLocationsRequest.php +++ b/app/Http/Requests/Api/Application/Locations/GetLocationsRequest.php @@ -7,13 +7,7 @@ class GetLocationsRequest extends ApplicationApiRequest { - /** - * @var string - */ - protected $resource = AdminAcl::RESOURCE_LOCATIONS; + protected ?string $resource = AdminAcl::RESOURCE_LOCATIONS; - /** - * @var int - */ - protected $permission = AdminAcl::READ; + protected int $permission = AdminAcl::READ; } diff --git a/app/Http/Requests/Api/Application/Locations/StoreLocationRequest.php b/app/Http/Requests/Api/Application/Locations/StoreLocationRequest.php index c5586ead11..cf0b126299 100644 --- a/app/Http/Requests/Api/Application/Locations/StoreLocationRequest.php +++ b/app/Http/Requests/Api/Application/Locations/StoreLocationRequest.php @@ -8,15 +8,9 @@ class StoreLocationRequest extends ApplicationApiRequest { - /** - * @var string - */ - protected $resource = AdminAcl::RESOURCE_LOCATIONS; + protected ?string $resource = AdminAcl::RESOURCE_LOCATIONS; - /** - * @var int - */ - protected $permission = AdminAcl::WRITE; + protected int $permission = AdminAcl::WRITE; /** * Rules to validate the request against. @@ -31,10 +25,8 @@ public function rules(): array /** * Rename fields to be more clear in error messages. - * - * @return array */ - public function attributes() + public function attributes(): array { return [ 'long' => 'Location Description', diff --git a/app/Http/Requests/Api/Application/Nests/Eggs/GetEggRequest.php b/app/Http/Requests/Api/Application/Nests/Eggs/GetEggRequest.php index e2ae0fc803..7c6eb54121 100644 --- a/app/Http/Requests/Api/Application/Nests/Eggs/GetEggRequest.php +++ b/app/Http/Requests/Api/Application/Nests/Eggs/GetEggRequest.php @@ -7,13 +7,7 @@ class GetEggRequest extends ApplicationApiRequest { - /** - * @var string - */ - protected $resource = AdminAcl::RESOURCE_EGGS; + protected ?string $resource = AdminAcl::RESOURCE_EGGS; - /** - * @var int - */ - protected $permission = AdminAcl::READ; + protected int $permission = AdminAcl::READ; } diff --git a/app/Http/Requests/Api/Application/Nests/Eggs/GetEggsRequest.php b/app/Http/Requests/Api/Application/Nests/Eggs/GetEggsRequest.php index a6aadf9043..b504af5b85 100644 --- a/app/Http/Requests/Api/Application/Nests/Eggs/GetEggsRequest.php +++ b/app/Http/Requests/Api/Application/Nests/Eggs/GetEggsRequest.php @@ -7,13 +7,7 @@ class GetEggsRequest extends ApplicationApiRequest { - /** - * @var string - */ - protected $resource = AdminAcl::RESOURCE_EGGS; + protected ?string $resource = AdminAcl::RESOURCE_EGGS; - /** - * @var int - */ - protected $permission = AdminAcl::READ; + protected int $permission = AdminAcl::READ; } diff --git a/app/Http/Requests/Api/Application/Nests/GetNestsRequest.php b/app/Http/Requests/Api/Application/Nests/GetNestsRequest.php index b90c457489..b4a990f9f7 100644 --- a/app/Http/Requests/Api/Application/Nests/GetNestsRequest.php +++ b/app/Http/Requests/Api/Application/Nests/GetNestsRequest.php @@ -7,13 +7,7 @@ class GetNestsRequest extends ApplicationApiRequest { - /** - * @var string - */ - protected $resource = AdminAcl::RESOURCE_NESTS; + protected ?string $resource = AdminAcl::RESOURCE_NESTS; - /** - * @var int - */ - protected $permission = AdminAcl::READ; + protected int $permission = AdminAcl::READ; } diff --git a/app/Http/Requests/Api/Application/Nodes/DeleteNodeRequest.php b/app/Http/Requests/Api/Application/Nodes/DeleteNodeRequest.php index 5b9bebf270..01f503f3f0 100644 --- a/app/Http/Requests/Api/Application/Nodes/DeleteNodeRequest.php +++ b/app/Http/Requests/Api/Application/Nodes/DeleteNodeRequest.php @@ -7,13 +7,7 @@ class DeleteNodeRequest extends ApplicationApiRequest { - /** - * @var string - */ - protected $resource = AdminAcl::RESOURCE_NODES; + protected ?string $resource = AdminAcl::RESOURCE_NODES; - /** - * @var int - */ - protected $permission = AdminAcl::WRITE; + protected int $permission = AdminAcl::WRITE; } diff --git a/app/Http/Requests/Api/Application/Nodes/GetDeployableNodesRequest.php b/app/Http/Requests/Api/Application/Nodes/GetDeployableNodesRequest.php index 0eae14008f..fd077ecd5b 100644 --- a/app/Http/Requests/Api/Application/Nodes/GetDeployableNodesRequest.php +++ b/app/Http/Requests/Api/Application/Nodes/GetDeployableNodesRequest.php @@ -4,9 +4,6 @@ class GetDeployableNodesRequest extends GetNodesRequest { - /** - * @return string[] - */ public function rules(): array { return [ diff --git a/app/Http/Requests/Api/Application/Nodes/GetNodesRequest.php b/app/Http/Requests/Api/Application/Nodes/GetNodesRequest.php index fc5f5a38e9..5c8524b945 100644 --- a/app/Http/Requests/Api/Application/Nodes/GetNodesRequest.php +++ b/app/Http/Requests/Api/Application/Nodes/GetNodesRequest.php @@ -7,13 +7,7 @@ class GetNodesRequest extends ApplicationApiRequest { - /** - * @var string - */ - protected $resource = AdminAcl::RESOURCE_NODES; + protected ?string $resource = AdminAcl::RESOURCE_NODES; - /** - * @var int - */ - protected $permission = AdminAcl::READ; + protected int $permission = AdminAcl::READ; } diff --git a/app/Http/Requests/Api/Application/Nodes/StoreNodeRequest.php b/app/Http/Requests/Api/Application/Nodes/StoreNodeRequest.php index 8c05c5a37e..bc559083e5 100644 --- a/app/Http/Requests/Api/Application/Nodes/StoreNodeRequest.php +++ b/app/Http/Requests/Api/Application/Nodes/StoreNodeRequest.php @@ -8,15 +8,9 @@ class StoreNodeRequest extends ApplicationApiRequest { - /** - * @var string - */ - protected $resource = AdminAcl::RESOURCE_NODES; + protected ?string $resource = AdminAcl::RESOURCE_NODES; - /** - * @var int - */ - protected $permission = AdminAcl::WRITE; + protected int $permission = AdminAcl::WRITE; /** * Validation rules to apply to this request. @@ -47,10 +41,8 @@ public function rules(array $rules = null): array /** * Fields to rename for clarity in the API response. - * - * @return array */ - public function attributes() + public function attributes(): array { return [ 'daemon_base' => 'Daemon Base Path', @@ -63,10 +55,8 @@ public function attributes() /** * Change the formatting of some data keys in the validated response data * to match what the application expects in the services. - * - * @return array */ - public function validated() + public function validated($key = null, $default = null): array { $response = parent::validated(); $response['daemonListen'] = $response['daemon_listen']; diff --git a/app/Http/Requests/Api/Application/Servers/Databases/GetServerDatabaseRequest.php b/app/Http/Requests/Api/Application/Servers/Databases/GetServerDatabaseRequest.php index 7761624403..01df4af328 100644 --- a/app/Http/Requests/Api/Application/Servers/Databases/GetServerDatabaseRequest.php +++ b/app/Http/Requests/Api/Application/Servers/Databases/GetServerDatabaseRequest.php @@ -7,13 +7,7 @@ class GetServerDatabaseRequest extends ApplicationApiRequest { - /** - * @var string - */ - protected $resource = AdminAcl::RESOURCE_SERVER_DATABASES; + protected ?string $resource = AdminAcl::RESOURCE_SERVER_DATABASES; - /** - * @var int - */ - protected $permission = AdminAcl::READ; + protected int $permission = AdminAcl::READ; } diff --git a/app/Http/Requests/Api/Application/Servers/Databases/GetServerDatabasesRequest.php b/app/Http/Requests/Api/Application/Servers/Databases/GetServerDatabasesRequest.php index 3e6cfc6fe6..ce72bbc20f 100644 --- a/app/Http/Requests/Api/Application/Servers/Databases/GetServerDatabasesRequest.php +++ b/app/Http/Requests/Api/Application/Servers/Databases/GetServerDatabasesRequest.php @@ -7,13 +7,7 @@ class GetServerDatabasesRequest extends ApplicationApiRequest { - /** - * @var string - */ - protected $resource = AdminAcl::RESOURCE_SERVER_DATABASES; + protected ?string $resource = AdminAcl::RESOURCE_SERVER_DATABASES; - /** - * @var int - */ - protected $permission = AdminAcl::READ; + protected int $permission = AdminAcl::READ; } diff --git a/app/Http/Requests/Api/Application/Servers/Databases/ServerDatabaseWriteRequest.php b/app/Http/Requests/Api/Application/Servers/Databases/ServerDatabaseWriteRequest.php index 917a5313fc..66cec82c39 100644 --- a/app/Http/Requests/Api/Application/Servers/Databases/ServerDatabaseWriteRequest.php +++ b/app/Http/Requests/Api/Application/Servers/Databases/ServerDatabaseWriteRequest.php @@ -6,8 +6,5 @@ class ServerDatabaseWriteRequest extends GetServerDatabasesRequest { - /** - * @var int - */ - protected $permission = AdminAcl::WRITE; + protected int $permission = AdminAcl::WRITE; } diff --git a/app/Http/Requests/Api/Application/Servers/Databases/StoreServerDatabaseRequest.php b/app/Http/Requests/Api/Application/Servers/Databases/StoreServerDatabaseRequest.php index c37de870d8..d53a0a75ef 100644 --- a/app/Http/Requests/Api/Application/Servers/Databases/StoreServerDatabaseRequest.php +++ b/app/Http/Requests/Api/Application/Servers/Databases/StoreServerDatabaseRequest.php @@ -12,15 +12,9 @@ class StoreServerDatabaseRequest extends ApplicationApiRequest { - /** - * @var string - */ - protected $resource = AdminAcl::RESOURCE_SERVER_DATABASES; + protected ?string $resource = AdminAcl::RESOURCE_SERVER_DATABASES; - /** - * @var int - */ - protected $permission = AdminAcl::WRITE; + protected int $permission = AdminAcl::WRITE; /** * Validation rules for database creation. @@ -46,10 +40,8 @@ public function rules(): array /** * Return data formatted in the correct format for the service to consume. - * - * @return array */ - public function validated() + public function validated($key = null, $default = null): array { return [ 'database' => $this->input('database'), @@ -60,10 +52,8 @@ public function validated() /** * Format error messages in a more understandable format for API output. - * - * @return array */ - public function attributes() + public function attributes(): array { return [ 'host' => 'Database Host Server ID', diff --git a/app/Http/Requests/Api/Application/Servers/GetExternalServerRequest.php b/app/Http/Requests/Api/Application/Servers/GetExternalServerRequest.php index 39ec449d28..50c9dabf82 100644 --- a/app/Http/Requests/Api/Application/Servers/GetExternalServerRequest.php +++ b/app/Http/Requests/Api/Application/Servers/GetExternalServerRequest.php @@ -7,13 +7,7 @@ class GetExternalServerRequest extends ApplicationApiRequest { - /** - * @var string - */ - protected $resource = AdminAcl::RESOURCE_SERVERS; + protected ?string $resource = AdminAcl::RESOURCE_SERVERS; - /** - * @var int - */ - protected $permission = AdminAcl::READ; + protected int $permission = AdminAcl::READ; } diff --git a/app/Http/Requests/Api/Application/Servers/GetServerRequest.php b/app/Http/Requests/Api/Application/Servers/GetServerRequest.php index 82d12687ca..63c4ea86a2 100644 --- a/app/Http/Requests/Api/Application/Servers/GetServerRequest.php +++ b/app/Http/Requests/Api/Application/Servers/GetServerRequest.php @@ -7,13 +7,7 @@ class GetServerRequest extends ApplicationApiRequest { - /** - * @var string - */ - protected $resource = AdminAcl::RESOURCE_SERVERS; + protected ?string $resource = AdminAcl::RESOURCE_SERVERS; - /** - * @var int - */ - protected $permission = AdminAcl::READ; + protected int $permission = AdminAcl::READ; } diff --git a/app/Http/Requests/Api/Application/Servers/ServerWriteRequest.php b/app/Http/Requests/Api/Application/Servers/ServerWriteRequest.php index 07c2013365..df2d76cd39 100644 --- a/app/Http/Requests/Api/Application/Servers/ServerWriteRequest.php +++ b/app/Http/Requests/Api/Application/Servers/ServerWriteRequest.php @@ -7,13 +7,7 @@ class ServerWriteRequest extends ApplicationApiRequest { - /** - * @var string - */ - protected $resource = AdminAcl::RESOURCE_SERVERS; + protected ?string $resource = AdminAcl::RESOURCE_SERVERS; - /** - * @var int - */ - protected $permission = AdminAcl::WRITE; + protected int $permission = AdminAcl::WRITE; } diff --git a/app/Http/Requests/Api/Application/Servers/StoreServerRequest.php b/app/Http/Requests/Api/Application/Servers/StoreServerRequest.php index ab2ec52f82..a9d0ecbed9 100644 --- a/app/Http/Requests/Api/Application/Servers/StoreServerRequest.php +++ b/app/Http/Requests/Api/Application/Servers/StoreServerRequest.php @@ -11,15 +11,9 @@ class StoreServerRequest extends ApplicationApiRequest { - /** - * @var string - */ - protected $resource = AdminAcl::RESOURCE_SERVERS; + protected ?string $resource = AdminAcl::RESOURCE_SERVERS; - /** - * @var int - */ - protected $permission = AdminAcl::WRITE; + protected int $permission = AdminAcl::WRITE; /** * Rules to be applied to this request. @@ -73,10 +67,8 @@ public function rules(): array /** * Normalize the data into a format that can be consumed by the service. - * - * @return array */ - public function validated() + public function validated($key = null, $default = null): array { $data = parent::validated(); @@ -119,7 +111,7 @@ public function withValidator(Validator $validator): void $query->whereNull('server_id'); }), ], function ($input) { - return !($input->deploy); + return !$input->deploy; }); $validator->sometimes('allocation.additional.*', [ @@ -128,7 +120,7 @@ public function withValidator(Validator $validator): void $query->whereNull('server_id'); }), ], function ($input) { - return !($input->deploy); + return !$input->deploy; }); $validator->sometimes('deploy.locations', 'present', function ($input) { @@ -142,10 +134,8 @@ public function withValidator(Validator $validator): void /** * Return a deployment object that can be passed to the server creation service. - * - * @return \Pterodactyl\Models\Objects\DeploymentObject|null */ - public function getDeploymentObject() + public function getDeploymentObject(): ?DeploymentObject { if (is_null($this->input('deploy'))) { return null; diff --git a/app/Http/Requests/Api/Application/Servers/UpdateServerBuildConfigurationRequest.php b/app/Http/Requests/Api/Application/Servers/UpdateServerBuildConfigurationRequest.php index f043f82b42..f1c977f11f 100644 --- a/app/Http/Requests/Api/Application/Servers/UpdateServerBuildConfigurationRequest.php +++ b/app/Http/Requests/Api/Application/Servers/UpdateServerBuildConfigurationRequest.php @@ -51,10 +51,8 @@ public function rules(): array /** * Convert the allocation field into the expected format for the service handler. - * - * @return array */ - public function validated() + public function validated($key = null, $default = null): array { $data = parent::validated(); @@ -78,10 +76,8 @@ public function validated() /** * Custom attributes to use in error message responses. - * - * @return array */ - public function attributes() + public function attributes(): array { return [ 'add_allocations' => 'allocations to add', @@ -99,11 +95,9 @@ public function attributes() * compatability with the old API endpoint while also supporting a more correct API * call. * - * @return array - * * @see https://github.com/pterodactyl/panel/issues/1500 */ - protected function requiredToOptional(string $field, array $rules, bool $limits = false) + protected function requiredToOptional(string $field, array $rules, bool $limits = false): array { if (!in_array('required', $rules)) { return $rules; diff --git a/app/Http/Requests/Api/Application/Servers/UpdateServerDetailsRequest.php b/app/Http/Requests/Api/Application/Servers/UpdateServerDetailsRequest.php index 3540b88cf2..aecc1cf02c 100644 --- a/app/Http/Requests/Api/Application/Servers/UpdateServerDetailsRequest.php +++ b/app/Http/Requests/Api/Application/Servers/UpdateServerDetailsRequest.php @@ -25,7 +25,7 @@ public function rules(): array * Convert the posted data into the correct format that is expected * by the application. */ - public function validated(): array + public function validated($key = null, $default = null): array { return [ 'external_id' => $this->input('external_id'), @@ -36,7 +36,7 @@ public function validated(): array } /** - * Rename some of the attributes in error messages to clarify the field + * Rename some attributes in error messages to clarify the field * being discussed. */ public function attributes(): array diff --git a/app/Http/Requests/Api/Application/Servers/UpdateServerStartupRequest.php b/app/Http/Requests/Api/Application/Servers/UpdateServerStartupRequest.php index c9b3c6ad09..985b10a6f6 100644 --- a/app/Http/Requests/Api/Application/Servers/UpdateServerStartupRequest.php +++ b/app/Http/Requests/Api/Application/Servers/UpdateServerStartupRequest.php @@ -8,15 +8,9 @@ class UpdateServerStartupRequest extends ApplicationApiRequest { - /** - * @var string - */ - protected $resource = AdminAcl::RESOURCE_SERVERS; + protected ?string $resource = AdminAcl::RESOURCE_SERVERS; - /** - * @var int - */ - protected $permission = AdminAcl::WRITE; + protected int $permission = AdminAcl::WRITE; /** * Validation rules to run the input against. @@ -36,10 +30,8 @@ public function rules(): array /** * Return the validated data in a format that is expected by the service. - * - * @return array */ - public function validated() + public function validated($key = null, $default = null): array { $data = parent::validated(); diff --git a/app/Http/Requests/Api/Application/Users/DeleteUserRequest.php b/app/Http/Requests/Api/Application/Users/DeleteUserRequest.php index c7592e693c..5e840a1c02 100644 --- a/app/Http/Requests/Api/Application/Users/DeleteUserRequest.php +++ b/app/Http/Requests/Api/Application/Users/DeleteUserRequest.php @@ -7,13 +7,7 @@ class DeleteUserRequest extends ApplicationApiRequest { - /** - * @var string - */ - protected $resource = AdminAcl::RESOURCE_USERS; + protected ?string $resource = AdminAcl::RESOURCE_USERS; - /** - * @var int - */ - protected $permission = AdminAcl::WRITE; + protected int $permission = AdminAcl::WRITE; } diff --git a/app/Http/Requests/Api/Application/Users/GetExternalUserRequest.php b/app/Http/Requests/Api/Application/Users/GetExternalUserRequest.php index a3bd7c9cfd..0f44aed3ff 100644 --- a/app/Http/Requests/Api/Application/Users/GetExternalUserRequest.php +++ b/app/Http/Requests/Api/Application/Users/GetExternalUserRequest.php @@ -7,13 +7,7 @@ class GetExternalUserRequest extends ApplicationApiRequest { - /** - * @var string - */ - protected $resource = AdminAcl::RESOURCE_USERS; + protected ?string $resource = AdminAcl::RESOURCE_USERS; - /** - * @var int - */ - protected $permission = AdminAcl::READ; + protected int $permission = AdminAcl::READ; } diff --git a/app/Http/Requests/Api/Application/Users/GetUsersRequest.php b/app/Http/Requests/Api/Application/Users/GetUsersRequest.php index 8736a8e9db..71ea30d338 100644 --- a/app/Http/Requests/Api/Application/Users/GetUsersRequest.php +++ b/app/Http/Requests/Api/Application/Users/GetUsersRequest.php @@ -7,13 +7,7 @@ class GetUsersRequest extends ApplicationApiRequest { - /** - * @var string - */ - protected $resource = Acl::RESOURCE_USERS; + protected ?string $resource = Acl::RESOURCE_USERS; - /** - * @var int - */ - protected $permission = Acl::READ; + protected int $permission = Acl::READ; } diff --git a/app/Http/Requests/Api/Application/Users/StoreUserRequest.php b/app/Http/Requests/Api/Application/Users/StoreUserRequest.php index 10a2d6b281..569dba3406 100644 --- a/app/Http/Requests/Api/Application/Users/StoreUserRequest.php +++ b/app/Http/Requests/Api/Application/Users/StoreUserRequest.php @@ -8,15 +8,9 @@ class StoreUserRequest extends ApplicationApiRequest { - /** - * @var string - */ - protected $resource = AdminAcl::RESOURCE_USERS; + protected ?string $resource = AdminAcl::RESOURCE_USERS; - /** - * @var int - */ - protected $permission = AdminAcl::WRITE; + protected int $permission = AdminAcl::WRITE; /** * Return the validation rules for this request. @@ -40,10 +34,7 @@ public function rules(array $rules = null): array return $response; } - /** - * @return array - */ - public function validated() + public function validated($key = null, $default = null): array { $data = parent::validated(); @@ -57,10 +48,8 @@ public function validated() /** * Rename some fields to be more user friendly. - * - * @return array */ - public function attributes() + public function attributes(): array { return [ 'external_id' => 'Third Party Identifier', diff --git a/app/Http/Requests/Api/Client/Servers/Backups/StoreBackupRequest.php b/app/Http/Requests/Api/Client/Servers/Backups/StoreBackupRequest.php index 5fbdaf728d..2871c039cf 100644 --- a/app/Http/Requests/Api/Client/Servers/Backups/StoreBackupRequest.php +++ b/app/Http/Requests/Api/Client/Servers/Backups/StoreBackupRequest.php @@ -7,10 +7,7 @@ class StoreBackupRequest extends ClientApiRequest { - /** - * @return string - */ - public function permission() + public function permission(): string { return Permission::ACTION_BACKUP_CREATE; } diff --git a/app/Http/Requests/Api/Client/Servers/Databases/StoreDatabaseRequest.php b/app/Http/Requests/Api/Client/Servers/Databases/StoreDatabaseRequest.php index 5258062f7c..be4f4a719d 100644 --- a/app/Http/Requests/Api/Client/Servers/Databases/StoreDatabaseRequest.php +++ b/app/Http/Requests/Api/Client/Servers/Databases/StoreDatabaseRequest.php @@ -43,10 +43,7 @@ public function rules(): array ]; } - /** - * @return array - */ - public function messages() + public function messages(): array { return [ 'database.unique' => 'The database name you have selected is already in use by this server.', diff --git a/app/Http/Requests/Api/Client/Servers/Files/ListFilesRequest.php b/app/Http/Requests/Api/Client/Servers/Files/ListFilesRequest.php index cc66d69b83..25443148f4 100644 --- a/app/Http/Requests/Api/Client/Servers/Files/ListFilesRequest.php +++ b/app/Http/Requests/Api/Client/Servers/Files/ListFilesRequest.php @@ -9,7 +9,7 @@ class ListFilesRequest extends ClientApiRequest { /** * Check that the user making this request to the API is authorized to list all - * of the files that exist for a given server. + * the files that exist for a given server. */ public function permission(): string { diff --git a/app/Http/Requests/Api/Client/Servers/Files/PullFileRequest.php b/app/Http/Requests/Api/Client/Servers/Files/PullFileRequest.php index 710ffcc7dd..5f76482476 100644 --- a/app/Http/Requests/Api/Client/Servers/Files/PullFileRequest.php +++ b/app/Http/Requests/Api/Client/Servers/Files/PullFileRequest.php @@ -13,9 +13,6 @@ public function permission(): string return Permission::ACTION_FILE_CREATE; } - /** - * @return string[] - */ public function rules(): array { return [ diff --git a/app/Http/Requests/Api/Client/Servers/Files/UploadFileRequest.php b/app/Http/Requests/Api/Client/Servers/Files/UploadFileRequest.php index 6808a54978..a591fdf68d 100644 --- a/app/Http/Requests/Api/Client/Servers/Files/UploadFileRequest.php +++ b/app/Http/Requests/Api/Client/Servers/Files/UploadFileRequest.php @@ -7,10 +7,7 @@ class UploadFileRequest extends ClientApiRequest { - /** - * @return string - */ - public function permission() + public function permission(): string { return Permission::ACTION_FILE_CREATE; } diff --git a/app/Http/Requests/Api/Client/Servers/Settings/ReinstallServerRequest.php b/app/Http/Requests/Api/Client/Servers/Settings/ReinstallServerRequest.php index 9edc8ad1c6..63f01dbae4 100644 --- a/app/Http/Requests/Api/Client/Servers/Settings/ReinstallServerRequest.php +++ b/app/Http/Requests/Api/Client/Servers/Settings/ReinstallServerRequest.php @@ -7,10 +7,7 @@ class ReinstallServerRequest extends ClientApiRequest { - /** - * @return string - */ - public function permission() + public function permission(): string { return Permission::ACTION_SETTINGS_REINSTALL; } diff --git a/app/Http/Requests/Api/Client/Servers/Settings/SetDockerImageRequest.php b/app/Http/Requests/Api/Client/Servers/Settings/SetDockerImageRequest.php index abe5d0436d..f618de3705 100644 --- a/app/Http/Requests/Api/Client/Servers/Settings/SetDockerImageRequest.php +++ b/app/Http/Requests/Api/Client/Servers/Settings/SetDockerImageRequest.php @@ -16,9 +16,6 @@ public function permission(): string return Permission::ACTION_STARTUP_DOCKER_IMAGE; } - /** - * @return array[] - */ public function rules(): array { /** @var \Pterodactyl\Models\Server $server */ diff --git a/app/Http/Requests/Api/Client/Servers/Startup/GetStartupRequest.php b/app/Http/Requests/Api/Client/Servers/Startup/GetStartupRequest.php index 25ab2ce21a..fee92bcbcb 100644 --- a/app/Http/Requests/Api/Client/Servers/Startup/GetStartupRequest.php +++ b/app/Http/Requests/Api/Client/Servers/Startup/GetStartupRequest.php @@ -7,10 +7,7 @@ class GetStartupRequest extends ClientApiRequest { - /** - * @return string - */ - public function permission() + public function permission(): string { return Permission::ACTION_STARTUP_READ; } diff --git a/app/Http/Requests/Api/Client/Servers/Startup/UpdateStartupVariableRequest.php b/app/Http/Requests/Api/Client/Servers/Startup/UpdateStartupVariableRequest.php index b46e6ea9a1..2c32f4df6d 100644 --- a/app/Http/Requests/Api/Client/Servers/Startup/UpdateStartupVariableRequest.php +++ b/app/Http/Requests/Api/Client/Servers/Startup/UpdateStartupVariableRequest.php @@ -7,18 +7,13 @@ class UpdateStartupVariableRequest extends ClientApiRequest { - /** - * @return string - */ - public function permission() + public function permission(): string { return Permission::ACTION_STARTUP_UPDATE; } /** * The actual validation of the variable's value will happen inside the controller. - * - * @return array|string[] */ public function rules(): array { diff --git a/app/Http/Requests/Api/Client/Servers/Subusers/DeleteSubuserRequest.php b/app/Http/Requests/Api/Client/Servers/Subusers/DeleteSubuserRequest.php index ef0a09aaaf..eabd84e616 100644 --- a/app/Http/Requests/Api/Client/Servers/Subusers/DeleteSubuserRequest.php +++ b/app/Http/Requests/Api/Client/Servers/Subusers/DeleteSubuserRequest.php @@ -6,10 +6,7 @@ class DeleteSubuserRequest extends SubuserRequest { - /** - * @return string - */ - public function permission() + public function permission(): string { return Permission::ACTION_USER_DELETE; } diff --git a/app/Http/Requests/Api/Client/Servers/Subusers/StoreSubuserRequest.php b/app/Http/Requests/Api/Client/Servers/Subusers/StoreSubuserRequest.php index 5bc93ab2fb..6e87225164 100644 --- a/app/Http/Requests/Api/Client/Servers/Subusers/StoreSubuserRequest.php +++ b/app/Http/Requests/Api/Client/Servers/Subusers/StoreSubuserRequest.php @@ -6,10 +6,7 @@ class StoreSubuserRequest extends SubuserRequest { - /** - * @return string - */ - public function permission() + public function permission(): string { return Permission::ACTION_USER_CREATE; } diff --git a/app/Http/Requests/Api/Client/Servers/Subusers/SubuserRequest.php b/app/Http/Requests/Api/Client/Servers/Subusers/SubuserRequest.php index 5a20084d3f..7c4fab9d22 100644 --- a/app/Http/Requests/Api/Client/Servers/Subusers/SubuserRequest.php +++ b/app/Http/Requests/Api/Client/Servers/Subusers/SubuserRequest.php @@ -4,16 +4,14 @@ use Illuminate\Http\Request; use Pterodactyl\Models\User; +use Pterodactyl\Models\Subuser; use Pterodactyl\Exceptions\Http\HttpForbiddenException; use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest; use Pterodactyl\Services\Servers\GetUserPermissionsService; abstract class SubuserRequest extends ClientApiRequest { - /** - * @var \Pterodactyl\Models\Subuser|null - */ - protected $model; + protected ?Subuser $model; /** * Authorize the request and ensure that a user is not trying to modify themselves. diff --git a/app/Http/Requests/Api/Client/Servers/Subusers/UpdateSubuserRequest.php b/app/Http/Requests/Api/Client/Servers/Subusers/UpdateSubuserRequest.php index 997b2daee2..bd8929a98d 100644 --- a/app/Http/Requests/Api/Client/Servers/Subusers/UpdateSubuserRequest.php +++ b/app/Http/Requests/Api/Client/Servers/Subusers/UpdateSubuserRequest.php @@ -6,10 +6,7 @@ class UpdateSubuserRequest extends SubuserRequest { - /** - * @return string - */ - public function permission() + public function permission(): string { return Permission::ACTION_USER_UPDATE; } diff --git a/app/Http/Requests/Api/Remote/ActivityEventRequest.php b/app/Http/Requests/Api/Remote/ActivityEventRequest.php index 795fe32e60..32ad11ad3f 100644 --- a/app/Http/Requests/Api/Remote/ActivityEventRequest.php +++ b/app/Http/Requests/Api/Remote/ActivityEventRequest.php @@ -27,9 +27,7 @@ public function rules(): array } /** - * Returns all of the unique server UUIDs that were recieved in this request. - * - * @return string[] + * Returns all the unique server UUIDs that were received in this request. */ public function servers(): array { @@ -37,9 +35,7 @@ public function servers(): array } /** - * Returns all of the unique user UUIDs that were submitted in this request. - * - * @return string[] + * Returns all the unique user UUIDs that were submitted in this request. */ public function users(): array { diff --git a/app/Http/Requests/Api/Remote/AuthenticateWebsocketDetailsRequest.php b/app/Http/Requests/Api/Remote/AuthenticateWebsocketDetailsRequest.php index 885e192397..1bae01dd7f 100644 --- a/app/Http/Requests/Api/Remote/AuthenticateWebsocketDetailsRequest.php +++ b/app/Http/Requests/Api/Remote/AuthenticateWebsocketDetailsRequest.php @@ -6,18 +6,12 @@ class AuthenticateWebsocketDetailsRequest extends FormRequest { - /** - * @return bool - */ - public function authorize() + public function authorize(): bool { return true; } - /** - * @return array - */ - public function rules() + public function rules(): array { return [ 'server_uuid' => 'required|string', diff --git a/app/Http/Requests/Api/Remote/InstallationDataRequest.php b/app/Http/Requests/Api/Remote/InstallationDataRequest.php index 0737d71b4e..c5d1973ec4 100644 --- a/app/Http/Requests/Api/Remote/InstallationDataRequest.php +++ b/app/Http/Requests/Api/Remote/InstallationDataRequest.php @@ -6,18 +6,12 @@ class InstallationDataRequest extends FormRequest { - /** - * @return bool - */ - public function authorize() + public function authorize(): bool { return true; } - /** - * @return array - */ - public function rules() + public function rules(): array { return [ 'successful' => 'present|boolean', diff --git a/app/Http/Requests/Api/Remote/ReportBackupCompleteRequest.php b/app/Http/Requests/Api/Remote/ReportBackupCompleteRequest.php index dcf7435c72..d0dd3090bb 100644 --- a/app/Http/Requests/Api/Remote/ReportBackupCompleteRequest.php +++ b/app/Http/Requests/Api/Remote/ReportBackupCompleteRequest.php @@ -6,10 +6,7 @@ class ReportBackupCompleteRequest extends FormRequest { - /** - * @return string[] - */ - public function rules() + public function rules(): array { return [ 'successful' => 'required|boolean', diff --git a/app/Http/Requests/Api/Remote/SftpAuthenticationFormRequest.php b/app/Http/Requests/Api/Remote/SftpAuthenticationFormRequest.php index f1e06cb3f5..964c27974b 100644 --- a/app/Http/Requests/Api/Remote/SftpAuthenticationFormRequest.php +++ b/app/Http/Requests/Api/Remote/SftpAuthenticationFormRequest.php @@ -8,20 +8,16 @@ class SftpAuthenticationFormRequest extends FormRequest { /** * Authenticate the request. - * - * @return bool */ - public function authorize() + public function authorize(): bool { return true; } /** * Rules to apply to the request. - * - * @return array */ - public function rules() + public function rules(): array { return [ 'type' => ['nullable', 'in:password,public_key'], @@ -33,10 +29,8 @@ public function rules() /** * Return only the fields that we are interested in from the request. * This will include empty fields as a null value. - * - * @return array */ - public function normalize() + public function normalize(): array { return $this->only( array_keys($this->rules()) diff --git a/app/Http/Requests/FrontendUserFormRequest.php b/app/Http/Requests/FrontendUserFormRequest.php index b5553dc16f..66d13d8c54 100644 --- a/app/Http/Requests/FrontendUserFormRequest.php +++ b/app/Http/Requests/FrontendUserFormRequest.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Http\Requests; @@ -13,14 +6,12 @@ abstract class FrontendUserFormRequest extends FormRequest { - abstract public function rules(); + abstract public function rules(): array; /** * Determine if a user is authorized to access this endpoint. - * - * @return bool */ - public function authorize() + public function authorize(): bool { return !is_null($this->user()); } @@ -28,10 +19,8 @@ public function authorize() /** * Return only the fields that we are interested in from the request. * This will include empty fields as a null value. - * - * @return array */ - public function normalize() + public function normalize(): array { return $this->only( array_keys($this->rules()) diff --git a/app/Http/Resources/Wings/ServerConfigurationCollection.php b/app/Http/Resources/Wings/ServerConfigurationCollection.php index fe352301c8..84fc2f8111 100644 --- a/app/Http/Resources/Wings/ServerConfigurationCollection.php +++ b/app/Http/Resources/Wings/ServerConfigurationCollection.php @@ -14,13 +14,9 @@ class ServerConfigurationCollection extends ResourceCollection * Converts a collection of Server models into an array of configuration responses * that can be understood by Wings. Make sure you've properly loaded the required * relationships on the Server models before calling this function, otherwise you'll - * have some serious performance issues from all of the N+1 queries. - * - * @param \Illuminate\Http\Request $request - * - * @return array + * have some serious performance issues from all the N+1 queries. */ - public function toArray($request) + public function toArray($request): array { $egg = Container::getInstance()->make(EggConfigurationService::class); $configuration = Container::getInstance()->make(ServerConfigurationStructureService::class); diff --git a/app/Http/ViewComposers/AssetComposer.php b/app/Http/ViewComposers/AssetComposer.php index 902c237b7d..d42f8a80ad 100644 --- a/app/Http/ViewComposers/AssetComposer.php +++ b/app/Http/ViewComposers/AssetComposer.php @@ -7,23 +7,17 @@ class AssetComposer { - /** - * @var \Pterodactyl\Services\Helpers\AssetHashService - */ - private $assetHashService; - /** * AssetComposer constructor. */ - public function __construct(AssetHashService $assetHashService) + public function __construct(private AssetHashService $assetHashService) { - $this->assetHashService = $assetHashService; } /** * Provide access to the asset service in the views. */ - public function compose(View $view) + public function compose(View $view): void { $view->with('asset', $this->assetHashService); $view->with('siteConfiguration', [ diff --git a/app/Jobs/Schedule/RunTaskJob.php b/app/Jobs/Schedule/RunTaskJob.php index 0b0f9e0754..1e1ab2bb3c 100644 --- a/app/Jobs/Schedule/RunTaskJob.php +++ b/app/Jobs/Schedule/RunTaskJob.php @@ -22,24 +22,12 @@ class RunTaskJob extends Job implements ShouldQueue use InteractsWithQueue; use SerializesModels; - /** - * @var \Pterodactyl\Models\Task - */ - public $task; - - /** - * @var bool - */ - public $manualRun; - /** * RunTaskJob constructor. */ - public function __construct(Task $task, $manualRun = false) + public function __construct(public Task $task, public bool $manualRun = false) { - $this->queue = config('pterodactyl.queues.standard'); - $this->task = $task; - $this->manualRun = $manualRun; + $this->queue = 'standard'; } /** diff --git a/app/Listeners/Auth/AuthenticationListener.php b/app/Listeners/Auth/AuthenticationListener.php index 888cf1d934..ec25b19c5b 100644 --- a/app/Listeners/Auth/AuthenticationListener.php +++ b/app/Listeners/Auth/AuthenticationListener.php @@ -2,9 +2,9 @@ namespace Pterodactyl\Listeners\Auth; -use Illuminate\Auth\Events\Login; use Pterodactyl\Facades\Activity; use Illuminate\Auth\Events\Failed; +use Pterodactyl\Events\Auth\DirectLogin; use Illuminate\Contracts\Events\Dispatcher; use Pterodactyl\Extensions\Illuminate\Events\Contracts\SubscribesToEvents; @@ -13,10 +13,8 @@ class AuthenticationListener implements SubscribesToEvents /** * Handles an authentication event by logging the user and information about * the request. - * - * @param \Illuminate\Auth\Events\Login|\Illuminate\Auth\Events\Failed $event */ - public function handle($event): void + public function handle(Failed|DirectLogin $event): void { $activity = Activity::withRequestMetadata(); if ($event->user) { @@ -35,6 +33,6 @@ public function handle($event): void public function subscribe(Dispatcher $events): void { $events->listen(Failed::class, self::class); - $events->listen(Login::class, self::class); + $events->listen(DirectLogin::class, self::class); } } diff --git a/app/Listeners/Auth/PasswordResetListener.php b/app/Listeners/Auth/PasswordResetListener.php index 7521a689b6..27ed19c694 100644 --- a/app/Listeners/Auth/PasswordResetListener.php +++ b/app/Listeners/Auth/PasswordResetListener.php @@ -15,7 +15,7 @@ public function __construct(Request $request) $this->request = $request; } - public function handle(PasswordReset $event) + public function handle(PasswordReset $event): void { Activity::event('event:password-reset') ->withRequestMetadata() diff --git a/app/Listeners/Auth/TwoFactorListener.php b/app/Listeners/Auth/TwoFactorListener.php index b9ab4c19a9..91d9208870 100644 --- a/app/Listeners/Auth/TwoFactorListener.php +++ b/app/Listeners/Auth/TwoFactorListener.php @@ -7,7 +7,7 @@ class TwoFactorListener { - public function handle(ProvidedAuthenticationToken $event) + public function handle(ProvidedAuthenticationToken $event): void { Activity::event($event->recovery ? 'auth:recovery-token' : 'auth:token') ->withRequestMetadata() diff --git a/app/Models/APILog.php b/app/Models/APILog.php index 359daa4ed2..673f94e738 100644 --- a/app/Models/APILog.php +++ b/app/Models/APILog.php @@ -8,29 +8,21 @@ class APILog extends Model { /** * The table associated with the model. - * - * @var string */ protected $table = 'api_logs'; /** * The attributes excluded from the model's JSON form. - * - * @var array */ protected $hidden = []; /** * Fields that are not mass assignable. - * - * @var array */ protected $guarded = ['id', 'created_at', 'updated_at']; /** * Cast values to correct type. - * - * @var array */ protected $casts = [ 'authorized' => 'boolean', diff --git a/app/Models/ActivityLog.php b/app/Models/ActivityLog.php index 36987c167f..7d900427a9 100644 --- a/app/Models/ActivityLog.php +++ b/app/Models/ActivityLog.php @@ -9,6 +9,7 @@ use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\MassPrunable; use Illuminate\Database\Eloquent\Relations\HasOne; +use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\MorphTo; use Illuminate\Database\Eloquent\Model as IlluminateModel; @@ -45,6 +46,7 @@ * @method static Builder|ActivityLog whereIp($value) * @method static Builder|ActivityLog whereProperties($value) * @method static Builder|ActivityLog whereTimestamp($value) + * * @mixin \Eloquent */ class ActivityLog extends Model @@ -54,7 +56,7 @@ class ActivityLog extends Model public const RESOURCE_NAME = 'activity_log'; /** - * Tracks all of the events we no longer wish to display to users. These are either legacy + * Tracks all the events we no longer wish to display to users. These are either legacy * events or just events where we never ended up using the associated data. */ public const DISABLED_EVENTS = ['server:file.upload']; @@ -73,7 +75,7 @@ class ActivityLog extends Model protected $with = ['subjects']; - public static $validationRules = [ + public static array $validationRules = [ 'event' => ['required', 'string'], 'batch' => ['nullable', 'uuid'], 'ip' => ['required', 'string'], @@ -91,7 +93,7 @@ public function actor(): MorphTo return $morph; } - public function subjects() + public function subjects(): HasMany { return $this->hasMany(ActivityLogSubject::class); } diff --git a/app/Models/ActivityLogSubject.php b/app/Models/ActivityLogSubject.php index 3147760349..6629b3e7d6 100644 --- a/app/Models/ActivityLogSubject.php +++ b/app/Models/ActivityLogSubject.php @@ -17,6 +17,7 @@ * @method static \Illuminate\Database\Eloquent\Builder|ActivityLogSubject newModelQuery() * @method static \Illuminate\Database\Eloquent\Builder|ActivityLogSubject newQuery() * @method static \Illuminate\Database\Eloquent\Builder|ActivityLogSubject query() + * * @mixin \Eloquent */ class ActivityLogSubject extends Pivot diff --git a/app/Models/Allocation.php b/app/Models/Allocation.php index 44b4f5bf12..d16e3150ce 100644 --- a/app/Models/Allocation.php +++ b/app/Models/Allocation.php @@ -2,6 +2,8 @@ namespace Pterodactyl\Models; +use Illuminate\Database\Eloquent\Relations\BelongsTo; + /** * Pterodactyl\Models\Allocation. * @@ -33,6 +35,7 @@ * @method static \Illuminate\Database\Eloquent\Builder|Allocation wherePort($value) * @method static \Illuminate\Database\Eloquent\Builder|Allocation whereServerId($value) * @method static \Illuminate\Database\Eloquent\Builder|Allocation whereUpdatedAt($value) + * * @mixin \Eloquent */ class Allocation extends Model @@ -45,22 +48,16 @@ class Allocation extends Model /** * The table associated with the model. - * - * @var string */ protected $table = 'allocations'; /** * Fields that are not mass assignable. - * - * @var array */ protected $guarded = ['id', 'created_at', 'updated_at']; /** * Cast values to correct type. - * - * @var array */ protected $casts = [ 'node_id' => 'integer', @@ -68,10 +65,7 @@ class Allocation extends Model 'server_id' => 'integer', ]; - /** - * @var array - */ - public static $validationRules = [ + public static array $validationRules = [ 'node_id' => 'required|exists:nodes,id', 'ip' => 'required|ip', 'port' => 'required|numeric|between:1024,65535', @@ -90,34 +84,24 @@ public function getRouteKeyName(): string /** * Return a hashid encoded string to represent the ID of the allocation. - * - * @return string */ - public function getHashidAttribute() + public function getHashidAttribute(): string { return app()->make('hashids')->encode($this->id); } /** * Accessor to automatically provide the IP alias if defined. - * - * @param string|null $value - * - * @return string */ - public function getAliasAttribute($value) + public function getAliasAttribute(?string $value): string { return (is_null($this->ip_alias)) ? $this->ip : $this->ip_alias; } /** * Accessor to quickly determine if this allocation has an alias. - * - * @param string|null $value - * - * @return bool */ - public function getHasAliasAttribute($value) + public function getHasAliasAttribute(?string $value): bool { return !is_null($this->ip_alias); } @@ -129,20 +113,16 @@ public function toString(): string /** * Gets information for the server associated with this allocation. - * - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ - public function server() + public function server(): BelongsTo { return $this->belongsTo(Server::class); } /** * Return the Node model associated with this allocation. - * - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ - public function node() + public function node(): BelongsTo { return $this->belongsTo(Node::class); } diff --git a/app/Models/ApiKey.php b/app/Models/ApiKey.php index e7f2f8efdf..5bfb89549a 100644 --- a/app/Models/ApiKey.php +++ b/app/Models/ApiKey.php @@ -55,6 +55,7 @@ * @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereToken($value) * @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereUpdatedAt($value) * @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereUserId($value) + * * @mixin \Eloquent */ class ApiKey extends Model @@ -87,15 +88,11 @@ class ApiKey extends Model /** * The table associated with the model. - * - * @var string */ protected $table = 'api_keys'; /** * Cast values to correct type. - * - * @var array */ protected $casts = [ 'allowed_ips' => 'array', @@ -113,8 +110,6 @@ class ApiKey extends Model /** * Fields that are mass assignable. - * - * @var array */ protected $fillable = [ 'identifier', @@ -127,17 +122,13 @@ class ApiKey extends Model /** * Fields that should not be included when calling toArray() or toJson() * on this model. - * - * @var array */ protected $hidden = ['token']; /** * Rules to protect against invalid data entry to DB. - * - * @var array */ - public static $validationRules = [ + public static array $validationRules = [ 'user_id' => 'required|exists:users,id', 'key_type' => 'present|integer|min:0|max:4', 'identifier' => 'required|string|size:16|unique:api_keys,identifier', @@ -157,9 +148,6 @@ class ApiKey extends Model 'r_' . AdminAcl::RESOURCE_SERVERS => 'integer|min:0|max:3', ]; - /** - * @var array - */ protected $dates = [ self::CREATED_AT, self::UPDATED_AT, @@ -177,23 +165,17 @@ public function user(): BelongsTo /** * Required for support with Laravel Sanctum. * - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo - * * @see \Laravel\Sanctum\Guard::supportsTokens() */ - public function tokenable() + public function tokenable(): BelongsTo { return $this->user(); } /** * Finds the model matching the provided token. - * - * @param string $token - * - * @return self|null */ - public static function findToken($token) + public static function findToken(string $token): ?self { $identifier = substr($token, 0, self::IDENTIFIER_LENGTH); diff --git a/app/Models/AuditLog.php b/app/Models/AuditLog.php index 6dea11f3cc..f42acbfffb 100644 --- a/app/Models/AuditLog.php +++ b/app/Models/AuditLog.php @@ -5,6 +5,7 @@ use Ramsey\Uuid\Uuid; use Illuminate\Http\Request; use Illuminate\Container\Container; +use Illuminate\Database\Eloquent\Relations\BelongsTo; /** * @deprecated — this class will be dropped in a future version, use the activity log @@ -13,10 +14,7 @@ class AuditLog extends Model { public const UPDATED_AT = null; - /** - * @var string[] - */ - public static $validationRules = [ + public static array $validationRules = [ 'uuid' => 'required|uuid', 'action' => 'required|string|max:191', 'subaction' => 'nullable|string|max:191', @@ -26,45 +24,27 @@ class AuditLog extends Model 'metadata' => 'array', ]; - /** - * @var string - */ protected $table = 'audit_logs'; - /** - * @var bool - */ - protected $immutableDates = true; + protected bool $immutableDates = true; - /** - * @var string[] - */ protected $casts = [ 'is_system' => 'bool', 'device' => 'array', 'metadata' => 'array', ]; - /** - * @var string[] - */ protected $guarded = [ 'id', 'created_at', ]; - /** - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo - */ - public function user() + public function user(): BelongsTo { return $this->belongsTo(User::class); } - /** - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo - */ - public function server() + public function server(): BelongsTo { return $this->belongsTo(Server::class); } @@ -74,11 +54,9 @@ public function server() * currently authenticated user if available. This model is not saved at this point, so * you can always make modifications to it as needed before saving. * - * @return $this - * * @deprecated */ - public static function instance(string $action, array $metadata, bool $isSystem = false) + public static function instance(string $action, array $metadata, bool $isSystem = false): self { /** @var \Illuminate\Http\Request $request */ $request = Container::getInstance()->make('request'); diff --git a/app/Models/Backup.php b/app/Models/Backup.php index 84ba680a21..41c94fee26 100644 --- a/app/Models/Backup.php +++ b/app/Models/Backup.php @@ -3,6 +3,7 @@ namespace Pterodactyl\Models; use Illuminate\Database\Eloquent\SoftDeletes; +use Illuminate\Database\Eloquent\Relations\BelongsTo; /** * @property int $id @@ -32,19 +33,10 @@ class Backup extends Model public const ADAPTER_WINGS = 'wings'; public const ADAPTER_AWS_S3 = 's3'; - /** - * @var string - */ protected $table = 'backups'; - /** - * @var bool - */ - protected $immutableDates = true; + protected bool $immutableDates = true; - /** - * @var array - */ protected $casts = [ 'id' => 'int', 'is_successful' => 'bool', @@ -53,16 +45,10 @@ class Backup extends Model 'bytes' => 'int', ]; - /** - * @var array - */ protected $dates = [ 'completed_at', ]; - /** - * @var array - */ protected $attributes = [ 'is_successful' => false, 'is_locked' => false, @@ -71,15 +57,9 @@ class Backup extends Model 'upload_id' => null, ]; - /** - * @var string[] - */ protected $guarded = ['id', 'created_at', 'updated_at', 'deleted_at']; - /** - * @var array - */ - public static $validationRules = [ + public static array $validationRules = [ 'server_id' => 'bail|required|numeric|exists:servers,id', 'uuid' => 'required|uuid', 'is_successful' => 'boolean', @@ -92,10 +72,7 @@ class Backup extends Model 'upload_id' => 'nullable|string', ]; - /** - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo - */ - public function server() + public function server(): BelongsTo { return $this->belongsTo(Server::class); } diff --git a/app/Models/Database.php b/app/Models/Database.php index e54d9f5ce2..e3856a4e2d 100644 --- a/app/Models/Database.php +++ b/app/Models/Database.php @@ -3,6 +3,7 @@ namespace Pterodactyl\Models; use Illuminate\Container\Container; +use Illuminate\Database\Eloquent\Relations\BelongsTo; use Pterodactyl\Contracts\Extensions\HashidsInterface; /** @@ -29,22 +30,16 @@ class Database extends Model /** * The table associated with the model. - * - * @var string */ protected $table = 'databases'; /** * The attributes excluded from the model's JSON form. - * - * @var array */ protected $hidden = ['password']; /** * Fields that are mass assignable. - * - * @var array */ protected $fillable = [ 'server_id', 'database_host_id', 'database', 'username', 'password', 'remote', 'max_connections', @@ -52,8 +47,6 @@ class Database extends Model /** * Cast values to correct type. - * - * @var array */ protected $casts = [ 'server_id' => 'integer', @@ -61,10 +54,7 @@ class Database extends Model 'max_connections' => 'integer', ]; - /** - * @var array - */ - public static $validationRules = [ + public static array $validationRules = [ 'server_id' => 'required|numeric|exists:servers,id', 'database_host_id' => 'required|exists:database_hosts,id', 'database' => 'required|string|alpha_dash|between:3,48', @@ -89,11 +79,9 @@ public function getRouteKeyName(): string * @param mixed $value * @param string|null $field * - * @return \Illuminate\Database\Eloquent\Model|null - * * @throws \Illuminate\Contracts\Container\BindingResolutionException */ - public function resolveRouteBinding($value, $field = null) + public function resolveRouteBinding($value, $field = null): ?\Illuminate\Database\Eloquent\Model { if (is_scalar($value) && ($field ?? $this->getRouteKeyName()) === 'id') { $value = ctype_digit((string) $value) @@ -106,20 +94,16 @@ public function resolveRouteBinding($value, $field = null) /** * Gets the host database server associated with a database. - * - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ - public function host() + public function host(): BelongsTo { return $this->belongsTo(DatabaseHost::class, 'database_host_id'); } /** * Gets the server associated with a database. - * - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ - public function server() + public function server(): BelongsTo { return $this->belongsTo(Server::class); } diff --git a/app/Models/DatabaseHost.php b/app/Models/DatabaseHost.php index eda5f84b83..df56eb7c72 100644 --- a/app/Models/DatabaseHost.php +++ b/app/Models/DatabaseHost.php @@ -2,6 +2,9 @@ namespace Pterodactyl\Models; +use Illuminate\Database\Eloquent\Relations\HasMany; +use Illuminate\Database\Eloquent\Relations\BelongsTo; + /** * @property int $id * @property string $name @@ -22,29 +25,20 @@ class DatabaseHost extends Model */ public const RESOURCE_NAME = 'database_host'; - /** - * @var bool - */ - protected $immutableDates = true; + protected bool $immutableDates = true; /** * The table associated with the model. - * - * @var string */ protected $table = 'database_hosts'; /** * The attributes excluded from the model's JSON form. - * - * @var array */ protected $hidden = ['password']; /** * Fields that are mass assignable. - * - * @var array */ protected $fillable = [ 'name', 'host', 'port', 'username', 'password', 'max_databases', 'node_id', @@ -52,8 +46,6 @@ class DatabaseHost extends Model /** * Cast values to correct type. - * - * @var array */ protected $casts = [ 'id' => 'integer', @@ -63,10 +55,8 @@ class DatabaseHost extends Model /** * Validation rules to assign to this model. - * - * @var array */ - public static $validationRules = [ + public static array $validationRules = [ 'name' => 'required|string|max:191', 'host' => 'required|string', 'port' => 'required|numeric|between:1,65535', @@ -77,20 +67,16 @@ class DatabaseHost extends Model /** * Gets the node associated with a database host. - * - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ - public function node() + public function node(): BelongsTo { return $this->belongsTo(Node::class); } /** * Gets the databases associated with this host. - * - * @return \Illuminate\Database\Eloquent\Relations\HasMany */ - public function databases() + public function databases(): HasMany { return $this->hasMany(Database::class); } diff --git a/app/Models/Egg.php b/app/Models/Egg.php index c7c08f117e..31c3e34200 100644 --- a/app/Models/Egg.php +++ b/app/Models/Egg.php @@ -2,6 +2,9 @@ namespace Pterodactyl\Models; +use Illuminate\Database\Eloquent\Relations\HasMany; +use Illuminate\Database\Eloquent\Relations\BelongsTo; + /** * @property int $id * @property string $uuid @@ -70,15 +73,11 @@ class Egg extends Model /** * The table associated with the model. - * - * @var string */ protected $table = 'eggs'; /** * Fields that are not mass assignable. - * - * @var array */ protected $fillable = [ 'name', @@ -102,8 +101,6 @@ class Egg extends Model /** * Cast values to correct type. - * - * @var array */ protected $casts = [ 'nest_id' => 'integer', @@ -116,10 +113,7 @@ class Egg extends Model 'file_denylist' => 'array', ]; - /** - * @var array - */ - public static $validationRules = [ + public static array $validationRules = [ 'nest_id' => 'required|bail|numeric|exists:nests,id', 'uuid' => 'required|string|size:36', 'name' => 'required|string|max:191', @@ -140,9 +134,6 @@ class Egg extends Model 'force_outgoing_ip' => 'sometimes|boolean', ]; - /** - * @var array - */ protected $attributes = [ 'features' => null, 'file_denylist' => null, @@ -156,10 +147,8 @@ class Egg extends Model /** * Returns the install script for the egg; if egg is copying from another * it will return the copied script. - * - * @return string */ - public function getCopyScriptInstallAttribute() + public function getCopyScriptInstallAttribute(): ?string { if (!is_null($this->script_install) || is_null($this->copy_script_from)) { return $this->script_install; @@ -171,10 +160,8 @@ public function getCopyScriptInstallAttribute() /** * Returns the entry command for the egg; if egg is copying from another * it will return the copied entry command. - * - * @return string */ - public function getCopyScriptEntryAttribute() + public function getCopyScriptEntryAttribute(): string { if (!is_null($this->script_entry) || is_null($this->copy_script_from)) { return $this->script_entry; @@ -186,10 +173,8 @@ public function getCopyScriptEntryAttribute() /** * Returns the install container for the egg; if egg is copying from another * it will return the copied install container. - * - * @return string */ - public function getCopyScriptContainerAttribute() + public function getCopyScriptContainerAttribute(): string { if (!is_null($this->script_container) || is_null($this->copy_script_from)) { return $this->script_container; @@ -200,10 +185,8 @@ public function getCopyScriptContainerAttribute() /** * Return the file configuration for an egg. - * - * @return string */ - public function getInheritConfigFilesAttribute() + public function getInheritConfigFilesAttribute(): ?string { if (!is_null($this->config_files) || is_null($this->config_from)) { return $this->config_files; @@ -214,10 +197,8 @@ public function getInheritConfigFilesAttribute() /** * Return the startup configuration for an egg. - * - * @return string */ - public function getInheritConfigStartupAttribute() + public function getInheritConfigStartupAttribute(): ?string { if (!is_null($this->config_startup) || is_null($this->config_from)) { return $this->config_startup; @@ -228,10 +209,8 @@ public function getInheritConfigStartupAttribute() /** * Return the log reading configuration for an egg. - * - * @return string */ - public function getInheritConfigLogsAttribute() + public function getInheritConfigLogsAttribute(): ?string { if (!is_null($this->config_logs) || is_null($this->config_from)) { return $this->config_logs; @@ -242,10 +221,8 @@ public function getInheritConfigLogsAttribute() /** * Return the stop command configuration for an egg. - * - * @return string */ - public function getInheritConfigStopAttribute() + public function getInheritConfigStopAttribute(): ?string { if (!is_null($this->config_stop) || is_null($this->config_from)) { return $this->config_stop; @@ -257,10 +234,8 @@ public function getInheritConfigStopAttribute() /** * Returns the features available to this egg from the parent configuration if there are * no features defined for this egg specifically and there is a parent egg configured. - * - * @return array|null */ - public function getInheritFeaturesAttribute() + public function getInheritFeaturesAttribute(): ?array { if (!is_null($this->features) || is_null($this->config_from)) { return $this->features; @@ -272,10 +247,8 @@ public function getInheritFeaturesAttribute() /** * Returns the features available to this egg from the parent configuration if there are * no features defined for this egg specifically and there is a parent egg configured. - * - * @return string[]|null */ - public function getInheritFileDenylistAttribute() + public function getInheritFileDenylistAttribute(): ?array { if (is_null($this->config_from)) { return $this->file_denylist; @@ -286,50 +259,40 @@ public function getInheritFileDenylistAttribute() /** * Gets nest associated with an egg. - * - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ - public function nest() + public function nest(): BelongsTo { return $this->belongsTo(Nest::class); } /** * Gets all servers associated with this egg. - * - * @return \Illuminate\Database\Eloquent\Relations\HasMany */ - public function servers() + public function servers(): HasMany { return $this->hasMany(Server::class, 'egg_id'); } /** * Gets all variables associated with this egg. - * - * @return \Illuminate\Database\Eloquent\Relations\HasMany */ - public function variables() + public function variables(): HasMany { return $this->hasMany(EggVariable::class, 'egg_id'); } /** * Get the parent egg from which to copy scripts. - * - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ - public function scriptFrom() + public function scriptFrom(): BelongsTo { return $this->belongsTo(self::class, 'copy_script_from'); } /** * Get the parent egg from which to copy configuration settings. - * - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ - public function configFrom() + public function configFrom(): BelongsTo { return $this->belongsTo(self::class, 'config_from'); } diff --git a/app/Models/EggMount.php b/app/Models/EggMount.php index cd85673ce4..ed494a13ed 100644 --- a/app/Models/EggMount.php +++ b/app/Models/EggMount.php @@ -4,18 +4,9 @@ class EggMount extends Model { - /** - * @var string - */ protected $table = 'egg_mount'; - /** - * @var null - */ protected $primaryKey = null; - /** - * @var bool - */ public $incrementing = false; } diff --git a/app/Models/EggVariable.php b/app/Models/EggVariable.php index 99966da3a3..8c34bb3980 100644 --- a/app/Models/EggVariable.php +++ b/app/Models/EggVariable.php @@ -2,6 +2,9 @@ namespace Pterodactyl\Models; +use Illuminate\Database\Eloquent\Relations\HasOne; +use Illuminate\Database\Eloquent\Relations\HasMany; + /** * @property int $id * @property int $egg_id @@ -32,34 +35,23 @@ class EggVariable extends Model /** * Reserved environment variable names. - * - * @var string */ public const RESERVED_ENV_NAMES = 'SERVER_MEMORY,SERVER_IP,SERVER_PORT,ENV,HOME,USER,STARTUP,SERVER_UUID,UUID'; - /** - * @var bool - */ - protected $immutableDates = true; + protected bool $immutableDates = true; /** * The table associated with the model. - * - * @var string */ protected $table = 'egg_variables'; /** * Fields that are not mass assignable. - * - * @var array */ protected $guarded = ['id', 'created_at', 'updated_at']; /** * Cast values to correct type. - * - * @var array */ protected $casts = [ 'egg_id' => 'integer', @@ -67,10 +59,7 @@ class EggVariable extends Model 'user_editable' => 'bool', ]; - /** - * @var array - */ - public static $validationRules = [ + public static array $validationRules = [ 'egg_id' => 'exists:eggs,id', 'name' => 'required|string|between:1,191', 'description' => 'string', @@ -81,36 +70,25 @@ class EggVariable extends Model 'rules' => 'required|string', ]; - /** - * @var array - */ protected $attributes = [ 'user_editable' => 0, 'user_viewable' => 0, ]; - /** - * @return bool - */ - public function getRequiredAttribute() + public function getRequiredAttribute(): bool { return in_array('required', explode('|', $this->rules)); } - /** - * @return \Illuminate\Database\Eloquent\Relations\HasOne - */ - public function egg() + public function egg(): HasOne { return $this->hasOne(Egg::class); } /** * Return server variables associated with this variable. - * - * @return \Illuminate\Database\Eloquent\Relations\HasMany */ - public function serverVariable() + public function serverVariable(): HasMany { return $this->hasMany(ServerVariable::class, 'variable_id'); } diff --git a/app/Models/Location.php b/app/Models/Location.php index c490e56caf..47a30354b3 100644 --- a/app/Models/Location.php +++ b/app/Models/Location.php @@ -2,6 +2,9 @@ namespace Pterodactyl\Models; +use Illuminate\Database\Eloquent\Relations\HasMany; +use Illuminate\Database\Eloquent\Relations\HasManyThrough; + /** * @property int $id * @property string $short @@ -21,24 +24,18 @@ class Location extends Model /** * The table associated with the model. - * - * @var string */ protected $table = 'locations'; /** * Fields that are not mass assignable. - * - * @var array */ protected $guarded = ['id', 'created_at', 'updated_at']; /** * Rules ensuring that the raw data stored in the database meets expectations. - * - * @var array */ - public static $validationRules = [ + public static array $validationRules = [ 'short' => 'required|string|between:1,60|unique:locations,short', 'long' => 'string|nullable|between:1,191', ]; @@ -53,20 +50,16 @@ public function getRouteKeyName(): string /** * Gets the nodes in a specified location. - * - * @return \Illuminate\Database\Eloquent\Relations\HasMany */ - public function nodes() + public function nodes(): HasMany { return $this->hasMany(Node::class); } /** * Gets the servers within a given location. - * - * @return \Illuminate\Database\Eloquent\Relations\HasManyThrough */ - public function servers() + public function servers(): HasManyThrough { return $this->hasManyThrough(Server::class, Node::class); } diff --git a/app/Models/Model.php b/app/Models/Model.php index 5c3488728e..5b028c67c0 100644 --- a/app/Models/Model.php +++ b/app/Models/Model.php @@ -4,13 +4,16 @@ use Illuminate\Support\Arr; use Illuminate\Support\Str; +use Carbon\CarbonImmutable; +use Illuminate\Support\Carbon; use Illuminate\Validation\Rule; use Illuminate\Container\Container; -use Illuminate\Contracts\Validation\Factory; +use Illuminate\Contracts\Validation\Validator; use Illuminate\Validation\ValidationException; use Illuminate\Database\Eloquent\Factories\HasFactory; use Pterodactyl\Exceptions\Model\DataValidationException; use Illuminate\Database\Eloquent\Model as IlluminateModel; +use Illuminate\Contracts\Validation\Factory as ValidationFactory; abstract class Model extends IlluminateModel { @@ -18,28 +21,18 @@ abstract class Model extends IlluminateModel /** * Set to true to return immutable Carbon date instances from the model. - * - * @var bool */ - protected $immutableDates = false; + protected bool $immutableDates = false; /** * Determines if the model should undergo data validation before it is saved * to the database. - * - * @var bool */ - protected $skipValidation = false; + protected bool $skipValidation = false; - /** - * @var \Illuminate\Contracts\Validation\Factory - */ - protected static $validatorFactory; + protected static ValidationFactory $validatorFactory; - /** - * @var array - */ - public static $validationRules = []; + public static array $validationRules = []; /** * Listen for the model saving event and fire off the validation @@ -51,7 +44,7 @@ protected static function boot() { parent::boot(); - static::$validatorFactory = Container::getInstance()->make(Factory::class); + static::$validatorFactory = Container::getInstance()->make(ValidationFactory::class); static::saving(function (Model $model) { try { @@ -65,7 +58,7 @@ protected static function boot() } /** - * Returns the model key to use for route model binding. By default we'll + * Returns the model key to use for route model binding. By default, we'll * assume every model uses a UUID field for this. If the model does not have * a UUID and is using a different key it should be specified on the model * itself. @@ -80,10 +73,8 @@ public function getRouteKeyName(): string /** * Set the model to skip validation when saving. - * - * @return $this */ - public function skipValidation() + public function skipValidation(): self { $this->skipValidation = true; @@ -92,10 +83,8 @@ public function skipValidation() /** * Returns the validator instance used by this model. - * - * @return \Illuminate\Validation\Validator|\Illuminate\Contracts\Validation\Validator */ - public function getValidator() + public function getValidator(): Validator { $rules = $this->exists ? static::getRulesForUpdate($this) : static::getRules(); @@ -104,10 +93,8 @@ public function getValidator() /** * Returns the rules associated with this model. - * - * @return array */ - public static function getRules() + public static function getRules(): array { $rules = static::$validationRules; foreach ($rules as $key => &$rule) { @@ -129,12 +116,8 @@ public static function getRulesForField(string $field): array /** * Returns the rules associated with the model, specifically for updating the given model * rather than just creating it. - * - * @param \Illuminate\Database\Eloquent\Model|int|string $model - * - * @return array */ - public static function getRulesForUpdate($model, string $column = 'id') + public static function getRulesForUpdate(IlluminateModel|int|string $model, string $column = 'id'): array { if ($model instanceof Model) { [$id, $column] = [$model->getKey(), $model->getKeyName()]; @@ -144,7 +127,7 @@ public static function getRulesForUpdate($model, string $column = 'id') foreach ($rules as $key => &$data) { // For each rule in a given field, iterate over it and confirm if the rule // is one for a unique field. If that is the case, append the ID of the current - // working model so we don't run into errors due to the way that field validation + // working model, so we don't run into errors due to the way that field validation // works. foreach ($data as &$datum) { if (!is_string($datum) || !Str::startsWith($datum, 'unique')) { @@ -163,6 +146,8 @@ public static function getRulesForUpdate($model, string $column = 'id') /** * Determines if the model is in a valid state or not. + * + * @throws \Illuminate\Validation\ValidationException */ public function validate(): void { @@ -172,9 +157,9 @@ public function validate(): void $validator = $this->getValidator(); $validator->setData( - // Trying to do self::toArray() here will leave out keys based on the whitelist/blacklist - // for that model. Doing this will return all of the attributes in a format that can - // properly be validated. + // Trying to do self::toArray() here will leave out keys based on the whitelist/blacklist + // for that model. Doing this will return all the attributes in a format that can + // properly be validated. $this->addCastAttributesToArray( $this->getAttributes(), $this->getMutatedAttributes() @@ -190,10 +175,8 @@ public function validate(): void * Return a timestamp as DateTime object. * * @param mixed $value - * - * @return \Illuminate\Support\Carbon|\Carbon\CarbonImmutable */ - protected function asDateTime($value) + protected function asDateTime($value): Carbon|CarbonImmutable { if (!$this->immutableDates) { return parent::asDateTime($value); diff --git a/app/Models/Mount.php b/app/Models/Mount.php index c1eb7a3a1a..475b3cb7f2 100644 --- a/app/Models/Mount.php +++ b/app/Models/Mount.php @@ -3,6 +3,7 @@ namespace Pterodactyl\Models; use Illuminate\Validation\Rules\NotIn; +use Illuminate\Database\Eloquent\Relations\BelongsToMany; /** * @property int $id @@ -27,22 +28,16 @@ class Mount extends Model /** * The table associated with the model. - * - * @var string */ protected $table = 'mounts'; /** * Fields that are not mass assignable. - * - * @var array */ protected $guarded = ['id', 'uuid']; /** * Default values for specific fields in the database. - * - * @var array */ protected $casts = [ 'id' => 'int', @@ -52,10 +47,8 @@ class Mount extends Model /** * Rules verifying that the data being stored matches the expectations of the database. - * - * @var string */ - public static $validationRules = [ + public static array $validationRules = [ 'name' => 'required|string|min:2|max:64|unique:mounts,name', 'description' => 'nullable|string|max:191', 'source' => 'required|string', @@ -68,7 +61,7 @@ class Mount extends Model * Implement language verification by overriding Eloquence's gather * rules function. */ - public static function getRules() + public static function getRules(): array { $rules = parent::getRules(); @@ -80,15 +73,11 @@ public static function getRules() /** * Disable timestamps on this model. - * - * @var bool */ public $timestamps = false; /** * Blacklisted source paths. - * - * @var string[] */ public static $invalidSourcePaths = [ '/etc/pterodactyl', @@ -98,8 +87,6 @@ public static function getRules() /** * Blacklisted target paths. - * - * @var string[] */ public static $invalidTargetPaths = [ '/home/container', @@ -107,30 +94,24 @@ public static function getRules() /** * Returns all eggs that have this mount assigned. - * - * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany */ - public function eggs() + public function eggs(): BelongsToMany { return $this->belongsToMany(Egg::class); } /** * Returns all nodes that have this mount assigned. - * - * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany */ - public function nodes() + public function nodes(): BelongsToMany { return $this->belongsToMany(Node::class); } /** * Returns all servers that have this mount assigned. - * - * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany */ - public function servers() + public function servers(): BelongsToMany { return $this->belongsToMany(Server::class); } diff --git a/app/Models/MountNode.php b/app/Models/MountNode.php index a897dd6dd5..3a189c46b5 100644 --- a/app/Models/MountNode.php +++ b/app/Models/MountNode.php @@ -6,18 +6,9 @@ class MountNode extends Model { - /** - * @var string - */ protected $table = 'mount_node'; - /** - * @var null - */ protected $primaryKey = null; - /** - * @var bool - */ public $incrementing = false; } diff --git a/app/Models/MountServer.php b/app/Models/MountServer.php index 21bf8fe2db..b9c0219e33 100644 --- a/app/Models/MountServer.php +++ b/app/Models/MountServer.php @@ -6,23 +6,11 @@ class MountServer extends Model { - /** - * @var string - */ protected $table = 'mount_server'; - /** - * @var bool - */ public $timestamps = false; - /** - * @var null - */ protected $primaryKey = null; - /** - * @var bool - */ public $incrementing = false; } diff --git a/app/Models/Nest.php b/app/Models/Nest.php index 1658171596..9a06ba2990 100644 --- a/app/Models/Nest.php +++ b/app/Models/Nest.php @@ -2,6 +2,8 @@ namespace Pterodactyl\Models; +use Illuminate\Database\Eloquent\Relations\HasMany; + /** * @property int $id * @property string $uuid @@ -23,25 +25,18 @@ class Nest extends Model /** * The table associated with the model. - * - * @var string */ protected $table = 'nests'; /** * Fields that are mass assignable. - * - * @var array */ protected $fillable = [ 'name', 'description', ]; - /** - * @var array - */ - public static $validationRules = [ + public static array $validationRules = [ 'author' => 'required|string|email', 'name' => 'required|string|max:191', 'description' => 'nullable|string', @@ -49,20 +44,16 @@ class Nest extends Model /** * Gets all eggs associated with this service. - * - * @return \Illuminate\Database\Eloquent\Relations\HasMany */ - public function eggs() + public function eggs(): HasMany { return $this->hasMany(Egg::class); } /** * Gets all servers associated with this nest. - * - * @return \Illuminate\Database\Eloquent\Relations\HasMany */ - public function servers() + public function servers(): HasMany { return $this->hasMany(Server::class); } diff --git a/app/Models/Node.php b/app/Models/Node.php index bb77834663..37aec760d6 100644 --- a/app/Models/Node.php +++ b/app/Models/Node.php @@ -7,6 +7,9 @@ use Illuminate\Container\Container; use Illuminate\Notifications\Notifiable; use Illuminate\Contracts\Encryption\Encrypter; +use Illuminate\Database\Eloquent\Relations\HasMany; +use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Database\Eloquent\Relations\HasManyThrough; /** * @property int $id @@ -51,22 +54,16 @@ class Node extends Model /** * The table associated with the model. - * - * @var string */ protected $table = 'nodes'; /** * The attributes excluded from the model's JSON form. - * - * @var array */ protected $hidden = ['daemon_token_id', 'daemon_token']; /** * Cast values to correct type. - * - * @var array */ protected $casts = [ 'location_id' => 'integer', @@ -81,8 +78,6 @@ class Node extends Model /** * Fields that are mass assignable. - * - * @var array */ protected $fillable = [ 'public', 'name', 'location_id', @@ -93,10 +88,7 @@ class Node extends Model 'description', 'maintenance_mode', ]; - /** - * @var array - */ - public static $validationRules = [ + public static array $validationRules = [ 'name' => 'required|regex:/^([\w .-]{1,100})$/', 'description' => 'string|nullable', 'location_id' => 'required|exists:locations,id', @@ -117,8 +109,6 @@ class Node extends Model /** * Default values for specific columns that are generally not changed on base installs. - * - * @var array */ protected $attributes = [ 'public' => true, @@ -141,10 +131,8 @@ public function getConnectionAddress(): string /** * Returns the configuration as an array. - * - * @return array */ - public function getConfiguration() + public function getConfiguration(): array { return [ 'debug' => false, @@ -174,20 +162,16 @@ public function getConfiguration() /** * Returns the configuration in Yaml format. - * - * @return string */ - public function getYamlConfiguration() + public function getYamlConfiguration(): string { return Yaml::dump($this->getConfiguration(), 4, 2, Yaml::DUMP_EMPTY_ARRAY_AS_SEQUENCE); } /** * Returns the configuration in JSON format. - * - * @return string */ - public function getJsonConfiguration(bool $pretty = false) + public function getJsonConfiguration(bool $pretty = false): string { return json_encode($this->getConfiguration(), $pretty ? JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT : JSON_UNESCAPED_SLASHES); } @@ -202,40 +186,31 @@ public function getDecryptedKey(): string ); } - /** - * @return \Illuminate\Database\Eloquent\Relations\HasManyThrough - */ - public function mounts() + public function mounts(): HasManyThrough { return $this->hasManyThrough(Mount::class, MountNode::class, 'node_id', 'id', 'id', 'mount_id'); } /** * Gets the location associated with a node. - * - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ - public function location() + public function location(): BelongsTo { return $this->belongsTo(Location::class); } /** * Gets the servers associated with a node. - * - * @return \Illuminate\Database\Eloquent\Relations\HasMany */ - public function servers() + public function servers(): HasMany { return $this->hasMany(Server::class); } /** * Gets the allocations associated with a node. - * - * @return \Illuminate\Database\Eloquent\Relations\HasMany */ - public function allocations() + public function allocations(): HasMany { return $this->hasMany(Allocation::class); } diff --git a/app/Models/Objects/DeploymentObject.php b/app/Models/Objects/DeploymentObject.php index 3c62f0baec..0c44be08e2 100644 --- a/app/Models/Objects/DeploymentObject.php +++ b/app/Models/Objects/DeploymentObject.php @@ -4,30 +4,18 @@ class DeploymentObject { - /** - * @var bool - */ - private $dedicated = false; + private bool $dedicated = false; - /** - * @var array - */ - private $locations = []; + private array $locations = []; - /** - * @var array - */ - private $ports = []; + private array $ports = []; public function isDedicated(): bool { return $this->dedicated; } - /** - * @return $this - */ - public function setDedicated(bool $dedicated) + public function setDedicated(bool $dedicated): self { $this->dedicated = $dedicated; @@ -39,10 +27,7 @@ public function getLocations(): array return $this->locations; } - /** - * @return $this - */ - public function setLocations(array $locations) + public function setLocations(array $locations): self { $this->locations = $locations; @@ -54,10 +39,7 @@ public function getPorts(): array return $this->ports; } - /** - * @return $this - */ - public function setPorts(array $ports) + public function setPorts(array $ports): self { $this->ports = $ports; diff --git a/app/Models/Permission.php b/app/Models/Permission.php index 26dc610dc1..6d27a90d30 100644 --- a/app/Models/Permission.php +++ b/app/Models/Permission.php @@ -67,51 +67,38 @@ class Permission extends Model /** * Should timestamps be used on this model. - * - * @var bool */ public $timestamps = false; /** * The table associated with the model. - * - * @var string */ protected $table = 'permissions'; /** * Fields that are not mass assignable. - * - * @var array */ protected $guarded = ['id', 'created_at', 'updated_at']; /** * Cast values to correct type. - * - * @var array */ protected $casts = [ 'subuser_id' => 'integer', ]; - /** - * @var array - */ - public static $validationRules = [ + public static array $validationRules = [ 'subuser_id' => 'required|numeric|min:1', 'permission' => 'required|string', ]; /** - * All of the permissions available on the system. You should use self::permissions() + * All the permissions available on the system. You should use self::permissions() * to retrieve them, and not directly access this array as it is subject to change. * - * @var array - * * @see \Pterodactyl\Models\Permission::permissions() */ - protected static $permissions = [ + protected static array $permissions = [ 'websocket' => [ 'description' => 'Allows the user to connect to the server websocket, giving them access to view console output and realtime server stats.', 'keys' => [ @@ -222,10 +209,8 @@ class Permission extends Model ]; /** - * Returns all of the permissions available on the system for a user to + * Returns all the permissions available on the system for a user to * have when controlling a server. - * - * @return \Illuminate\Database\Eloquent\Collection */ public static function permissions(): Collection { diff --git a/app/Models/RecoveryToken.php b/app/Models/RecoveryToken.php index 0244c7bca4..d4ca764a08 100644 --- a/app/Models/RecoveryToken.php +++ b/app/Models/RecoveryToken.php @@ -2,6 +2,8 @@ namespace Pterodactyl\Models; +use Illuminate\Database\Eloquent\Relations\BelongsTo; + /** * @property int $id * @property int $user_id @@ -16,27 +18,15 @@ class RecoveryToken extends Model */ public const UPDATED_AT = null; - /** - * @var bool - */ public $timestamps = true; - /** - * @var bool - */ - protected $immutableDates = true; + protected bool $immutableDates = true; - /** - * @var string[] - */ - public static $validationRules = [ + public static array $validationRules = [ 'token' => 'required|string', ]; - /** - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo - */ - public function user() + public function user(): BelongsTo { return $this->belongsTo(User::class); } diff --git a/app/Models/Schedule.php b/app/Models/Schedule.php index 81c30768c0..183ebb82be 100644 --- a/app/Models/Schedule.php +++ b/app/Models/Schedule.php @@ -5,6 +5,8 @@ use Cron\CronExpression; use Carbon\CarbonImmutable; use Illuminate\Container\Container; +use Illuminate\Database\Eloquent\Relations\HasMany; +use Illuminate\Database\Eloquent\Relations\BelongsTo; use Pterodactyl\Contracts\Extensions\HashidsInterface; /** @@ -37,22 +39,16 @@ class Schedule extends Model /** * The table associated with the model. - * - * @var string */ protected $table = 'schedules'; /** * Always return the tasks associated with this schedule. - * - * @var array */ protected $with = ['tasks']; /** * Mass assignable attributes on this model. - * - * @var array */ protected $fillable = [ 'server_id', @@ -69,9 +65,6 @@ class Schedule extends Model 'next_run_at', ]; - /** - * @var array - */ protected $casts = [ 'id' => 'integer', 'server_id' => 'integer', @@ -82,17 +75,12 @@ class Schedule extends Model /** * Columns to mutate to a date. - * - * @var array */ protected $dates = [ 'last_run_at', 'next_run_at', ]; - /** - * @var array - */ protected $attributes = [ 'name' => null, 'cron_day_of_week' => '*', @@ -105,10 +93,7 @@ class Schedule extends Model 'only_when_online' => false, ]; - /** - * @var array - */ - public static $validationRules = [ + public static array $validationRules = [ 'server_id' => 'required|exists:servers,id', 'name' => 'required|string|max:191', 'cron_day_of_week' => 'required|string', @@ -134,11 +119,9 @@ public function getRouteKeyName(): string /** * Returns the schedule's execution crontab entry as a string. * - * @return \Carbon\CarbonImmutable - * * @throws \Exception */ - public function getNextRunDate() + public function getNextRunDate(): CarbonImmutable { $formatted = sprintf('%s %s %s %s %s', $this->cron_minute, $this->cron_hour, $this->cron_day_of_month, $this->cron_month, $this->cron_day_of_week); @@ -149,30 +132,24 @@ public function getNextRunDate() /** * Return a hashid encoded string to represent the ID of the schedule. - * - * @return string */ - public function getHashidAttribute() + public function getHashidAttribute(): string { return Container::getInstance()->make(HashidsInterface::class)->encode($this->id); } /** * Return tasks belonging to a schedule. - * - * @return \Illuminate\Database\Eloquent\Relations\HasMany */ - public function tasks() + public function tasks(): HasMany { return $this->hasMany(Task::class); } /** * Return the server model that a schedule belongs to. - * - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ - public function server() + public function server(): BelongsTo { return $this->belongsTo(Server::class); } diff --git a/app/Models/Server.php b/app/Models/Server.php index b1560bb286..5ad99151a3 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -5,7 +5,11 @@ use Illuminate\Notifications\Notifiable; use Illuminate\Database\Query\JoinClause; use Znck\Eloquent\Traits\BelongsToThrough; +use Illuminate\Database\Eloquent\Relations\HasOne; +use Illuminate\Database\Eloquent\Relations\HasMany; +use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\MorphToMany; +use Illuminate\Database\Eloquent\Relations\HasManyThrough; use Pterodactyl\Exceptions\Http\Server\ServerStateConflictException; /** @@ -95,6 +99,7 @@ * @method static \Illuminate\Database\Eloquent\Builder|Server whereUpdatedAt($value) * @method static \Illuminate\Database\Eloquent\Builder|Server whereUuid($value) * @method static \Illuminate\Database\Eloquent\Builder|Server whereUuidShort($value) + * * @mixin \Eloquent */ class Server extends Model @@ -115,16 +120,12 @@ class Server extends Model /** * The table associated with the model. - * - * @var string */ protected $table = 'servers'; /** * Default values when creating the model. We want to switch to disabling OOM killer * on server instances unless the user specifies otherwise in the request. - * - * @var array */ protected $attributes = [ 'status' => self::STATUS_INSTALLING, @@ -134,29 +135,20 @@ class Server extends Model /** * The default relationships to load for all server models. - * - * @var string[] */ protected $with = ['allocation']; /** * The attributes that should be mutated to dates. - * - * @var array */ protected $dates = [self::CREATED_AT, self::UPDATED_AT, 'deleted_at', 'installed_at']; /** * Fields that are not mass assignable. - * - * @var array */ protected $guarded = ['id', self::CREATED_AT, self::UPDATED_AT, 'deleted_at', 'installed_at']; - /** - * @var array - */ - public static $validationRules = [ + public static array $validationRules = [ 'external_id' => 'sometimes|nullable|string|between:1,191|unique:servers', 'owner_id' => 'required|integer|exists:users,id', 'name' => 'required|string|min:1|max:191', @@ -183,8 +175,6 @@ class Server extends Model /** * Cast values to correct type. - * - * @var array */ protected $casts = [ 'node_id' => 'integer', @@ -226,76 +216,62 @@ public function isSuspended(): bool /** * Gets the user who owns the server. - * - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ - public function user() + public function user(): BelongsTo { return $this->belongsTo(User::class, 'owner_id'); } /** * Gets the subusers associated with a server. - * - * @return \Illuminate\Database\Eloquent\Relations\HasMany */ - public function subusers() + public function subusers(): HasMany { return $this->hasMany(Subuser::class, 'server_id', 'id'); } /** * Gets the default allocation for a server. - * - * @return \Illuminate\Database\Eloquent\Relations\HasOne */ - public function allocation() + public function allocation(): HasOne { return $this->hasOne(Allocation::class, 'id', 'allocation_id'); } /** * Gets all allocations associated with this server. - * - * @return \Illuminate\Database\Eloquent\Relations\HasMany */ - public function allocations() + public function allocations(): HasMany { return $this->hasMany(Allocation::class, 'server_id'); } /** * Gets information for the nest associated with this server. - * - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ - public function nest() + public function nest(): BelongsTo { return $this->belongsTo(Nest::class); } /** * Gets information for the egg associated with this server. - * - * @return \Illuminate\Database\Eloquent\Relations\HasOne */ - public function egg() + public function egg(): HasOne { return $this->hasOne(Egg::class, 'id', 'egg_id'); } /** * Gets information for the service variables associated with this server. - * - * @return \Illuminate\Database\Eloquent\Relations\HasMany */ - public function variables() + public function variables(): HasMany { return $this->hasMany(EggVariable::class, 'egg_id', 'egg_id') ->select(['egg_variables.*', 'server_variables.variable_value as server_value']) ->leftJoin('server_variables', function (JoinClause $join) { // Don't forget to join against the server ID as well since the way we're using this relationship - // would actually return all of the variables and their values for _all_ servers using that egg,\ + // would actually return all the variables and their values for _all_ servers using that egg, // rather than only the server for this model. // // @see https://github.com/pterodactyl/panel/issues/2250 @@ -306,30 +282,24 @@ public function variables() /** * Gets information for the node associated with this server. - * - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ - public function node() + public function node(): BelongsTo { return $this->belongsTo(Node::class); } /** * Gets information for the tasks associated with this server. - * - * @return \Illuminate\Database\Eloquent\Relations\HasMany */ - public function schedules() + public function schedules(): HasMany { return $this->hasMany(Schedule::class); } /** * Gets all databases associated with a server. - * - * @return \Illuminate\Database\Eloquent\Relations\HasMany */ - public function databases() + public function databases(): HasMany { return $this->hasMany(Database::class); } @@ -337,39 +307,30 @@ public function databases() /** * Returns the location that a server belongs to. * - * @return \Znck\Eloquent\Relations\BelongsToThrough - * * @throws \Exception */ - public function location() + public function location(): \Znck\Eloquent\Relations\BelongsToThrough { return $this->belongsToThrough(Location::class, Node::class); } /** * Returns the associated server transfer. - * - * @return \Illuminate\Database\Eloquent\Relations\HasOne */ - public function transfer() + public function transfer(): HasOne { return $this->hasOne(ServerTransfer::class)->whereNull('successful')->orderByDesc('id'); } - /** - * @return \Illuminate\Database\Eloquent\Relations\HasMany - */ - public function backups() + public function backups(): HasMany { return $this->hasMany(Backup::class); } /** * Returns all mounts that have this server has mounted. - * - * @return \Illuminate\Database\Eloquent\Relations\HasManyThrough */ - public function mounts() + public function mounts(): HasManyThrough { return $this->hasManyThrough(Mount::class, MountServer::class, 'server_id', 'id', 'id', 'mount_id'); } diff --git a/app/Models/ServerTransfer.php b/app/Models/ServerTransfer.php index 7e297b6015..da46188282 100644 --- a/app/Models/ServerTransfer.php +++ b/app/Models/ServerTransfer.php @@ -2,6 +2,9 @@ namespace Pterodactyl\Models; +use Illuminate\Database\Eloquent\Relations\HasOne; +use Illuminate\Database\Eloquent\Relations\BelongsTo; + /** * @property int $id * @property int $server_id @@ -29,22 +32,16 @@ class ServerTransfer extends Model /** * The table associated with the model. - * - * @var string */ protected $table = 'server_transfers'; /** * Fields that are not mass assignable. - * - * @var array */ protected $guarded = ['id', 'created_at', 'updated_at']; /** * Cast values to correct type. - * - * @var array */ protected $casts = [ 'server_id' => 'int', @@ -58,10 +55,7 @@ class ServerTransfer extends Model 'archived' => 'bool', ]; - /** - * @var array - */ - public static $validationRules = [ + public static array $validationRules = [ 'server_id' => 'required|numeric|exists:servers,id', 'old_node' => 'required|numeric', 'new_node' => 'required|numeric', @@ -76,30 +70,24 @@ class ServerTransfer extends Model /** * Gets the server associated with a server transfer. - * - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ - public function server() + public function server(): BelongsTo { return $this->belongsTo(Server::class); } /** * Gets the source node associated with a server transfer. - * - * @return \Illuminate\Database\Eloquent\Relations\HasOne */ - public function oldNode() + public function oldNode(): HasOne { return $this->hasOne(Node::class, 'id', 'old_node'); } /** * Gets the target node associated with a server transfer. - * - * @return \Illuminate\Database\Eloquent\Relations\HasOne */ - public function newNode() + public function newNode(): HasOne { return $this->hasOne(Node::class, 'id', 'new_node'); } diff --git a/app/Models/ServerVariable.php b/app/Models/ServerVariable.php index 9b3ad81faf..46ae57dd4b 100644 --- a/app/Models/ServerVariable.php +++ b/app/Models/ServerVariable.php @@ -2,6 +2,8 @@ namespace Pterodactyl\Models; +use Illuminate\Database\Eloquent\Relations\BelongsTo; + /** * @property int $id * @property int $server_id @@ -20,23 +22,18 @@ class ServerVariable extends Model */ public const RESOURCE_NAME = 'server_variable'; - /** @var bool */ - protected $immutableDates = true; + protected bool $immutableDates = true; - /** @var string */ protected $table = 'server_variables'; - /** @var string[] */ protected $guarded = ['id', 'created_at', 'updated_at']; - /** @var string[] */ protected $casts = [ 'server_id' => 'integer', 'variable_id' => 'integer', ]; - /** @var string[] */ - public static $validationRules = [ + public static array $validationRules = [ 'server_id' => 'required|int', 'variable_id' => 'required|int', 'variable_value' => 'string', @@ -44,20 +41,16 @@ class ServerVariable extends Model /** * Returns the server this variable is associated with. - * - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ - public function server() + public function server(): BelongsTo { return $this->belongsTo(Server::class); } /** * Returns information about a given variables parent. - * - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ - public function variable() + public function variable(): BelongsTo { return $this->belongsTo(EggVariable::class, 'variable_id'); } diff --git a/app/Models/Session.php b/app/Models/Session.php index 535afc8016..baacdb8753 100644 --- a/app/Models/Session.php +++ b/app/Models/Session.php @@ -8,15 +8,11 @@ class Session extends Model { /** * The table associated with the model. - * - * @var string */ protected $table = 'sessions'; /** * Cast values to correct type. - * - * @var array */ protected $casts = [ 'id' => 'string', diff --git a/app/Models/Setting.php b/app/Models/Setting.php index 4582486285..52c7f1cffb 100644 --- a/app/Models/Setting.php +++ b/app/Models/Setting.php @@ -6,25 +6,14 @@ class Setting extends Model { /** * The table associated with the model. - * - * @var string */ protected $table = 'settings'; - /** - * @var bool - */ public $timestamps = false; - /** - * @var array - */ protected $fillable = ['key', 'value']; - /** - * @var array - */ - public static $validationRules = [ + public static array $validationRules = [ 'key' => 'required|string|between:1,191', 'value' => 'string', ]; diff --git a/app/Models/Subuser.php b/app/Models/Subuser.php index 8dcdaf37ce..40950d5b2d 100644 --- a/app/Models/Subuser.php +++ b/app/Models/Subuser.php @@ -3,6 +3,8 @@ namespace Pterodactyl\Models; use Illuminate\Notifications\Notifiable; +use Illuminate\Database\Eloquent\Relations\HasMany; +use Illuminate\Database\Eloquent\Relations\BelongsTo; /** * @property int $id @@ -26,22 +28,16 @@ class Subuser extends Model /** * The table associated with the model. - * - * @var string */ protected $table = 'subusers'; /** * Fields that are not mass assignable. - * - * @var array */ protected $guarded = ['id', 'created_at', 'updated_at']; /** * Cast values to correct type. - * - * @var array */ protected $casts = [ 'user_id' => 'int', @@ -49,10 +45,7 @@ class Subuser extends Model 'permissions' => 'array', ]; - /** - * @var array - */ - public static $validationRules = [ + public static array $validationRules = [ 'user_id' => 'required|numeric|exists:users,id', 'server_id' => 'required|numeric|exists:servers,id', 'permissions' => 'nullable|array', @@ -61,40 +54,32 @@ class Subuser extends Model /** * Return a hashid encoded string to represent the ID of the subuser. - * - * @return string */ - public function getHashidAttribute() + public function getHashidAttribute(): string { return app()->make('hashids')->encode($this->id); } /** * Gets the server associated with a subuser. - * - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ - public function server() + public function server(): BelongsTo { return $this->belongsTo(Server::class); } /** * Gets the user associated with a subuser. - * - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ - public function user() + public function user(): BelongsTo { return $this->belongsTo(User::class); } /** * Gets the permissions associated with a subuser. - * - * @return \Illuminate\Database\Eloquent\Relations\HasMany */ - public function permissions() + public function permissions(): HasMany { return $this->hasMany(Permission::class); } diff --git a/app/Models/Task.php b/app/Models/Task.php index b02503286a..8e772731e1 100644 --- a/app/Models/Task.php +++ b/app/Models/Task.php @@ -4,6 +4,7 @@ use Illuminate\Container\Container; use Znck\Eloquent\Traits\BelongsToThrough; +use Illuminate\Database\Eloquent\Relations\BelongsTo; use Pterodactyl\Contracts\Extensions\HashidsInterface; /** @@ -40,22 +41,16 @@ class Task extends Model /** * The table associated with the model. - * - * @var string */ protected $table = 'tasks'; /** * Relationships to be updated when this model is updated. - * - * @var array */ protected $touches = ['schedule']; /** * Fields that are mass assignable. - * - * @var array */ protected $fillable = [ 'schedule_id', @@ -69,8 +64,6 @@ class Task extends Model /** * Cast values to correct type. - * - * @var array */ protected $casts = [ 'id' => 'integer', @@ -83,8 +76,6 @@ class Task extends Model /** * Default attributes when creating a new model. - * - * @var array */ protected $attributes = [ 'time_offset' => 0, @@ -92,10 +83,7 @@ class Task extends Model 'continue_on_failure' => false, ]; - /** - * @var array - */ - public static $validationRules = [ + public static array $validationRules = [ 'schedule_id' => 'required|numeric|exists:schedules,id', 'sequence_id' => 'required|numeric|min:1', 'action' => 'required|string', @@ -115,32 +103,24 @@ public function getRouteKeyName(): string /** * Return a hashid encoded string to represent the ID of the task. - * - * @return string */ - public function getHashidAttribute() + public function getHashidAttribute(): string { return Container::getInstance()->make(HashidsInterface::class)->encode($this->id); } /** * Return the schedule that a task belongs to. - * - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ - public function schedule() + public function schedule(): BelongsTo { return $this->belongsTo(Schedule::class); } /** * Return the server a task is assigned to, acts as a belongsToThrough. - * - * @return \Znck\Eloquent\Relations\BelongsToThrough - * - * @throws \Exception */ - public function server() + public function server(): \Znck\Eloquent\Relations\BelongsToThrough { return $this->belongsToThrough(Server::class, Schedule::class); } diff --git a/app/Models/TaskLog.php b/app/Models/TaskLog.php index eabfde604c..0efd77ff47 100644 --- a/app/Models/TaskLog.php +++ b/app/Models/TaskLog.php @@ -8,22 +8,16 @@ class TaskLog extends Model { /** * The table associated with the model. - * - * @var string */ protected $table = 'tasks_log'; /** * Fields that are not mass assignable. - * - * @var array */ protected $guarded = ['id', 'created_at', 'updated_at']; /** * Cast values to correct type. - * - * @var array */ protected $casts = [ 'id' => 'integer', @@ -33,8 +27,6 @@ class TaskLog extends Model /** * The attributes that should be mutated to dates. - * - * @var array */ protected $dates = ['run_time', 'created_at', 'updated_at']; } diff --git a/app/Models/User.php b/app/Models/User.php index b75039819b..df8271cf4f 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -75,6 +75,7 @@ * @method static Builder|User whereUseTotp($value) * @method static Builder|User whereUsername($value) * @method static Builder|User whereUuid($value) + * * @mixin \Eloquent */ class User extends Model implements @@ -100,22 +101,16 @@ class User extends Model implements /** * Level of servers to display when using access() on a user. - * - * @var string */ - protected $accessLevel = 'all'; + protected string $accessLevel = 'all'; /** * The table associated with the model. - * - * @var string */ protected $table = 'users'; /** * A list of mass-assignable variables. - * - * @var array */ protected $fillable = [ 'external_id', @@ -134,8 +129,6 @@ class User extends Model implements /** * Cast values to correct type. - * - * @var array */ protected $casts = [ 'root_admin' => 'boolean', @@ -143,22 +136,15 @@ class User extends Model implements 'gravatar' => 'boolean', ]; - /** - * @var array - */ protected $dates = ['totp_authenticated_at']; /** * The attributes excluded from the model's JSON form. - * - * @var array */ protected $hidden = ['password', 'remember_token', 'totp_secret', 'totp_authenticated_at']; /** * Default values for specific fields in the database. - * - * @var array */ protected $attributes = [ 'external_id' => null, @@ -170,10 +156,8 @@ class User extends Model implements /** * Rules verifying that the data being stored matches the expectations of the database. - * - * @var array */ - public static $validationRules = [ + public static array $validationRules = [ 'uuid' => 'required|string|size:36|unique:users,uuid', 'email' => 'required|email|between:1,191|unique:users,email', 'external_id' => 'sometimes|nullable|string|max:191|unique:users,external_id', @@ -191,7 +175,7 @@ class User extends Model implements * Implement language verification by overriding Eloquence's gather * rules function. */ - public static function getRules() + public static function getRules(): array { $rules = parent::getRules(); @@ -234,37 +218,27 @@ public function setUsernameAttribute(string $value) /** * Return a concatenated result for the accounts full name. - * - * @return string */ - public function getNameAttribute() + public function getNameAttribute(): string { return trim($this->name_first . ' ' . $this->name_last); } /** * Returns all servers that a user owns. - * - * @return \Illuminate\Database\Eloquent\Relations\HasMany */ - public function servers() + public function servers(): HasMany { return $this->hasMany(Server::class, 'owner_id'); } - /** - * @return \Illuminate\Database\Eloquent\Relations\HasMany - */ - public function apiKeys() + public function apiKeys(): HasMany { return $this->hasMany(ApiKey::class) ->where('key_type', ApiKey::TYPE_ACCOUNT); } - /** - * @return \Illuminate\Database\Eloquent\Relations\HasMany - */ - public function recoveryTokens() + public function recoveryTokens(): HasMany { return $this->hasMany(RecoveryToken::class); } @@ -275,7 +249,7 @@ public function sshKeys(): HasMany } /** - * Returns all of the activity logs where this user is the subject — not to + * Returns all the activity logs where this user is the subject — not to * be confused by activity logs where this user is the _actor_. */ public function activity(): MorphToMany @@ -284,12 +258,10 @@ public function activity(): MorphToMany } /** - * Returns all of the servers that a user can access by way of being the owner of the + * Returns all the servers that a user can access by way of being the owner of the * server, or because they are assigned as a subuser for that server. - * - * @return \Illuminate\Database\Eloquent\Builder */ - public function accessibleServers() + public function accessibleServers(): Builder { return Server::query() ->select('servers.*') diff --git a/app/Models/UserSSHKey.php b/app/Models/UserSSHKey.php index 658bac6c0f..0aac52310e 100644 --- a/app/Models/UserSSHKey.php +++ b/app/Models/UserSSHKey.php @@ -50,7 +50,7 @@ class UserSSHKey extends Model 'fingerprint', ]; - public static $validationRules = [ + public static array $validationRules = [ 'name' => ['required', 'string'], 'fingerprint' => ['required', 'string'], 'public_key' => ['required', 'string'], diff --git a/app/Notifications/AccountCreated.php b/app/Notifications/AccountCreated.php index e52bed5fec..fb13380121 100644 --- a/app/Notifications/AccountCreated.php +++ b/app/Notifications/AccountCreated.php @@ -12,50 +12,25 @@ class AccountCreated extends Notification implements ShouldQueue { use Queueable; - /** - * The authentication token to be used for the user to set their - * password for the first time. - * - * @var string|null - */ - public $token; - - /** - * The user model for the created user. - * - * @var \Pterodactyl\Models\User - */ - public $user; - /** * Create a new notification instance. */ - public function __construct(User $user, string $token = null) + public function __construct(public User $user, public ?string $token = null) { - $this->token = $token; - $this->user = $user; } /** * Get the notification's delivery channels. - * - * @param mixed $notifiable - * - * @return array */ - public function via($notifiable) + public function via(): array { return ['mail']; } /** * Get the mail representation of the notification. - * - * @param mixed $notifiable - * - * @return \Illuminate\Notifications\Messages\MailMessage */ - public function toMail($notifiable) + public function toMail(): MailMessage { $message = (new MailMessage()) ->greeting('Hello ' . $this->user->name . '!') diff --git a/app/Notifications/AddedToServer.php b/app/Notifications/AddedToServer.php index c116a2e294..c78c7db8be 100644 --- a/app/Notifications/AddedToServer.php +++ b/app/Notifications/AddedToServer.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Notifications; @@ -18,10 +11,7 @@ class AddedToServer extends Notification implements ShouldQueue { use Queueable; - /** - * @var object - */ - public $server; + public object $server; /** * Create a new notification instance. @@ -33,24 +23,16 @@ public function __construct(array $server) /** * Get the notification's delivery channels. - * - * @param mixed $notifiable - * - * @return array */ - public function via($notifiable) + public function via(): array { return ['mail']; } /** * Get the mail representation of the notification. - * - * @param mixed $notifiable - * - * @return \Illuminate\Notifications\Messages\MailMessage */ - public function toMail($notifiable) + public function toMail(): MailMessage { return (new MailMessage()) ->greeting('Hello ' . $this->server->user . '!') diff --git a/app/Notifications/MailTested.php b/app/Notifications/MailTested.php index d0f083accd..892ff7b0a2 100644 --- a/app/Notifications/MailTested.php +++ b/app/Notifications/MailTested.php @@ -8,22 +8,16 @@ class MailTested extends Notification { - /** - * @var \Pterodactyl\Models\User - */ - private $user; - - public function __construct(User $user) + public function __construct(private User $user) { - $this->user = $user; } - public function via() + public function via(): array { return ['mail']; } - public function toMail() + public function toMail(): MailMessage { return (new MailMessage()) ->subject('Pterodactyl Test Message') diff --git a/app/Notifications/RemovedFromServer.php b/app/Notifications/RemovedFromServer.php index 24418e51ce..492dc60e6d 100644 --- a/app/Notifications/RemovedFromServer.php +++ b/app/Notifications/RemovedFromServer.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Notifications; @@ -18,10 +11,7 @@ class RemovedFromServer extends Notification implements ShouldQueue { use Queueable; - /** - * @var object - */ - public $server; + public object $server; /** * Create a new notification instance. @@ -33,24 +23,16 @@ public function __construct(array $server) /** * Get the notification's delivery channels. - * - * @param mixed $notifiable - * - * @return array */ - public function via($notifiable) + public function via(): array { return ['mail']; } /** * Get the mail representation of the notification. - * - * @param mixed $notifiable - * - * @return \Illuminate\Notifications\Messages\MailMessage */ - public function toMail($notifiable) + public function toMail(): MailMessage { return (new MailMessage()) ->error() diff --git a/app/Notifications/SendPasswordReset.php b/app/Notifications/SendPasswordReset.php index cd6d46050a..b424c8293b 100644 --- a/app/Notifications/SendPasswordReset.php +++ b/app/Notifications/SendPasswordReset.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Notifications; @@ -18,43 +11,25 @@ class SendPasswordReset extends Notification implements ShouldQueue { use Queueable; - /** - * The password reset token. - * - * @var string - */ - public $token; - /** * Create a new notification instance. - * - * @param string $token */ - public function __construct($token) + public function __construct(public string $token) { - $this->token = $token; } /** * Get the notification's delivery channels. - * - * @param mixed $notifiable - * - * @return array */ - public function via($notifiable) + public function via(): array { return ['mail']; } /** * Get the mail representation of the notification. - * - * @param mixed $notifiable - * - * @return \Illuminate\Notifications\Messages\MailMessage */ - public function toMail($notifiable) + public function toMail(mixed $notifiable): MailMessage { return (new MailMessage()) ->subject('Reset Password') diff --git a/app/Notifications/ServerInstalled.php b/app/Notifications/ServerInstalled.php index cc5a943138..46f38ff84e 100644 --- a/app/Notifications/ServerInstalled.php +++ b/app/Notifications/ServerInstalled.php @@ -2,9 +2,12 @@ namespace Pterodactyl\Notifications; +use Pterodactyl\Models\User; use Illuminate\Bus\Queueable; use Pterodactyl\Events\Event; +use Pterodactyl\Models\Server; use Illuminate\Container\Container; +use Pterodactyl\Events\Server\Installed; use Illuminate\Notifications\Notification; use Illuminate\Contracts\Queue\ShouldQueue; use Pterodactyl\Contracts\Core\ReceivesEvents; @@ -15,23 +18,15 @@ class ServerInstalled extends Notification implements ShouldQueue, ReceivesEvent { use Queueable; - /** - * @var \Pterodactyl\Models\Server - */ - public $server; + public Server $server; - /** - * @var \Pterodactyl\Models\User - */ - public $user; + public User $user; /** * Handle a direct call to this notification from the server installed event. This is configured * in the event service provider. - * - * @param \Pterodactyl\Events\Event|\Pterodactyl\Events\Server\Installed $event */ - public function handle(Event $event): void + public function handle(Event|Installed $event): void { $event->server->loadMissing('user'); @@ -45,20 +40,16 @@ public function handle(Event $event): void /** * Get the notification's delivery channels. - * - * @return array */ - public function via() + public function via(): array { return ['mail']; } /** * Get the mail representation of the notification. - * - * @return \Illuminate\Notifications\Messages\MailMessage */ - public function toMail() + public function toMail(): MailMessage { return (new MailMessage()) ->greeting('Hello ' . $this->user->username . '.') diff --git a/app/Observers/ServerObserver.php b/app/Observers/ServerObserver.php index c6a6212048..51276523ef 100644 --- a/app/Observers/ServerObserver.php +++ b/app/Observers/ServerObserver.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Observers; @@ -20,7 +13,7 @@ class ServerObserver /** * Listen to the Server creating event. */ - public function creating(Server $server) + public function creating(Server $server): void { event(new Events\Server\Creating($server)); } @@ -28,7 +21,7 @@ public function creating(Server $server) /** * Listen to the Server created event. */ - public function created(Server $server) + public function created(Server $server): void { event(new Events\Server\Created($server)); } @@ -36,7 +29,7 @@ public function created(Server $server) /** * Listen to the Server deleting event. */ - public function deleting(Server $server) + public function deleting(Server $server): void { event(new Events\Server\Deleting($server)); } @@ -44,7 +37,7 @@ public function deleting(Server $server) /** * Listen to the Server deleted event. */ - public function deleted(Server $server) + public function deleted(Server $server): void { event(new Events\Server\Deleted($server)); } @@ -52,7 +45,7 @@ public function deleted(Server $server) /** * Listen to the Server saving event. */ - public function saving(Server $server) + public function saving(Server $server): void { event(new Events\Server\Saving($server)); } @@ -60,7 +53,7 @@ public function saving(Server $server) /** * Listen to the Server saved event. */ - public function saved(Server $server) + public function saved(Server $server): void { event(new Events\Server\Saved($server)); } @@ -68,7 +61,7 @@ public function saved(Server $server) /** * Listen to the Server updating event. */ - public function updating(Server $server) + public function updating(Server $server): void { event(new Events\Server\Updating($server)); } @@ -76,7 +69,7 @@ public function updating(Server $server) /** * Listen to the Server saved event. */ - public function updated(Server $server) + public function updated(Server $server): void { event(new Events\Server\Updated($server)); } diff --git a/app/Observers/SubuserObserver.php b/app/Observers/SubuserObserver.php index 128de77fb3..f1e028b96f 100644 --- a/app/Observers/SubuserObserver.php +++ b/app/Observers/SubuserObserver.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Observers; @@ -19,7 +12,7 @@ class SubuserObserver /** * Listen to the Subuser creating event. */ - public function creating(Subuser $subuser) + public function creating(Subuser $subuser): void { event(new Events\Subuser\Creating($subuser)); } @@ -27,21 +20,21 @@ public function creating(Subuser $subuser) /** * Listen to the Subuser created event. */ - public function created(Subuser $subuser) + public function created(Subuser $subuser): void { event(new Events\Subuser\Created($subuser)); - $subuser->user->notify((new AddedToServer([ + $subuser->user->notify(new AddedToServer([ 'user' => $subuser->user->name_first, 'name' => $subuser->server->name, 'uuidShort' => $subuser->server->uuidShort, - ]))); + ])); } /** * Listen to the Subuser deleting event. */ - public function deleting(Subuser $subuser) + public function deleting(Subuser $subuser): void { event(new Events\Subuser\Deleting($subuser)); } @@ -49,13 +42,13 @@ public function deleting(Subuser $subuser) /** * Listen to the Subuser deleted event. */ - public function deleted(Subuser $subuser) + public function deleted(Subuser $subuser): void { event(new Events\Subuser\Deleted($subuser)); - $subuser->user->notify((new RemovedFromServer([ + $subuser->user->notify(new RemovedFromServer([ 'user' => $subuser->user->name_first, 'name' => $subuser->server->name, - ]))); + ])); } } diff --git a/app/Observers/UserObserver.php b/app/Observers/UserObserver.php index 18d73bc106..dd017a22d3 100644 --- a/app/Observers/UserObserver.php +++ b/app/Observers/UserObserver.php @@ -7,12 +7,12 @@ class UserObserver { - protected $uuid; + protected string $uuid; /** * Listen to the User creating event. */ - public function creating(User $user) + public function creating(User $user): void { event(new Events\User\Creating($user)); } @@ -20,7 +20,7 @@ public function creating(User $user) /** * Listen to the User created event. */ - public function created(User $user) + public function created(User $user): void { event(new Events\User\Created($user)); } @@ -28,7 +28,7 @@ public function created(User $user) /** * Listen to the User deleting event. */ - public function deleting(User $user) + public function deleting(User $user): void { event(new Events\User\Deleting($user)); } @@ -36,7 +36,7 @@ public function deleting(User $user) /** * Listen to the User deleted event. */ - public function deleted(User $user) + public function deleted(User $user): void { event(new Events\User\Deleted($user)); } diff --git a/app/Policies/ServerPolicy.php b/app/Policies/ServerPolicy.php index d72b7bd140..e3565bb250 100644 --- a/app/Policies/ServerPolicy.php +++ b/app/Policies/ServerPolicy.php @@ -22,12 +22,8 @@ protected function checkPermission(User $user, Server $server, string $permissio /** * Runs before any of the functions are called. Used to determine if user is root admin, if so, ignore permissions. - * - * @param string $ability - * - * @return bool */ - public function before(User $user, $ability, Server $server) + public function before(User $user, string $ability, Server $server): bool { if ($user->root_admin || $server->owner_id === $user->id) { return true; @@ -40,11 +36,8 @@ public function before(User $user, $ability, Server $server) * This is a horrendous hack to avoid Laravel's "smart" behavior that does * not call the before() function if there isn't a function matching the * policy permission. - * - * @param string $name - * @param mixed $arguments */ - public function __call($name, $arguments) + public function __call(string $name, mixed $arguments) { // do nothing } diff --git a/app/Providers/ActivityLogServiceProvider.php b/app/Providers/ActivityLogServiceProvider.php index b410b4c80c..7a3de759c6 100644 --- a/app/Providers/ActivityLogServiceProvider.php +++ b/app/Providers/ActivityLogServiceProvider.php @@ -3,7 +3,7 @@ namespace Pterodactyl\Providers; use Illuminate\Support\ServiceProvider; -use Pterodactyl\Services\Activity\AcitvityLogBatchService; +use Pterodactyl\Services\Activity\ActivityLogBatchService; use Pterodactyl\Services\Activity\ActivityLogTargetableService; class ActivityLogServiceProvider extends ServiceProvider @@ -14,7 +14,7 @@ class ActivityLogServiceProvider extends ServiceProvider */ public function register() { - $this->app->scoped(AcitvityLogBatchService::class); + $this->app->scoped(ActivityLogBatchService::class); $this->app->scoped(ActivityLogTargetableService::class); } } diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 5b38a41757..d4ffdadbbd 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -69,10 +69,8 @@ public function register() /** * Return version information for the footer. - * - * @return array */ - protected function versionData() + protected function versionData(): array { return Cache::remember('git-version', 5, function () { if (file_exists(base_path('.git/HEAD'))) { diff --git a/app/Providers/AuthServiceProvider.php b/app/Providers/AuthServiceProvider.php index bc95e9206d..fed77e15ae 100644 --- a/app/Providers/AuthServiceProvider.php +++ b/app/Providers/AuthServiceProvider.php @@ -11,9 +11,7 @@ class AuthServiceProvider extends ServiceProvider { /** - * The policy mappings for the application. - * - * @var array + * The model to policy mappings for the application. */ protected $policies = [ Server::class => ServerPolicy::class, diff --git a/app/Providers/BackupsServiceProvider.php b/app/Providers/BackupsServiceProvider.php index 999dfa90d4..3b22a20693 100644 --- a/app/Providers/BackupsServiceProvider.php +++ b/app/Providers/BackupsServiceProvider.php @@ -18,10 +18,7 @@ public function register() }); } - /** - * @return string[] - */ - public function provides() + public function provides(): array { return [BackupManager::class]; } diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index 9a02cca3d7..6f91f24f54 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -18,9 +18,7 @@ class EventServiceProvider extends ServiceProvider { /** - * The event listener mappings for the application. - * - * @var array + * The event to listener mappings for the application. */ protected $listen = [ ServerInstalledEvent::class => [ServerInstalledNotification::class], @@ -31,9 +29,9 @@ class EventServiceProvider extends ServiceProvider ]; /** - * Boots the service provider and registers model event listeners. + * Register any events for your application. */ - public function boot() + public function boot(): void { parent::boot(); @@ -42,9 +40,4 @@ public function boot() Subuser::observe(SubuserObserver::class); EggVariable::observe(EggVariableObserver::class); } - - public function shouldDiscoverEvents() - { - return true; - } } diff --git a/app/Providers/HashidsServiceProvider.php b/app/Providers/HashidsServiceProvider.php index f2b3935d83..f094878d0f 100644 --- a/app/Providers/HashidsServiceProvider.php +++ b/app/Providers/HashidsServiceProvider.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Providers; diff --git a/app/Providers/SettingsServiceProvider.php b/app/Providers/SettingsServiceProvider.php index 447ac3db13..5508ee0fbc 100644 --- a/app/Providers/SettingsServiceProvider.php +++ b/app/Providers/SettingsServiceProvider.php @@ -15,10 +15,8 @@ class SettingsServiceProvider extends ServiceProvider /** * An array of configuration keys to override with database values * if they exist. - * - * @var array */ - protected $keys = [ + protected array $keys = [ 'app:name', 'app:locale', 'recaptcha:enabled', @@ -37,10 +35,8 @@ class SettingsServiceProvider extends ServiceProvider /** * Keys specific to the mail driver that are only grabbed from the database * when using the SMTP driver. - * - * @var array */ - protected $emailKeys = [ + protected array $emailKeys = [ 'mail:host', 'mail:port', 'mail:from:address', @@ -53,10 +49,8 @@ class SettingsServiceProvider extends ServiceProvider /** * Keys that are encrypted and should be decrypted when set in the * configuration array. - * - * @var array */ - protected static $encrypted = [ + protected static array $encrypted = [ 'mail:password', ]; diff --git a/app/Repositories/Eloquent/AllocationRepository.php b/app/Repositories/Eloquent/AllocationRepository.php index 3723c1d1fa..6eb8b6d1e5 100644 --- a/app/Repositories/Eloquent/AllocationRepository.php +++ b/app/Repositories/Eloquent/AllocationRepository.php @@ -10,16 +10,14 @@ class AllocationRepository extends EloquentRepository implements AllocationRepos { /** * Return the model backing this repository. - * - * @return string */ - public function model() + public function model(): string { return Allocation::class; } /** - * Return all of the allocations that exist for a node that are not currently + * Return all the allocations that exist for a node that are not currently * allocated. */ public function getUnassignedAllocationIds(int $node): array @@ -57,10 +55,8 @@ protected function getDiscardableDedicatedAllocations(array $nodes = []): array /** * Return a single allocation from those meeting the requirements. - * - * @return \Pterodactyl\Models\Allocation|null */ - public function getRandomAllocation(array $nodes, array $ports, bool $dedicated = false) + public function getRandomAllocation(array $nodes, array $ports, bool $dedicated = false): ?Allocation { $query = Allocation::query()->whereNull('server_id'); diff --git a/app/Repositories/Eloquent/ApiKeyRepository.php b/app/Repositories/Eloquent/ApiKeyRepository.php index 67a7e6e6e9..eb1a362aed 100644 --- a/app/Repositories/Eloquent/ApiKeyRepository.php +++ b/app/Repositories/Eloquent/ApiKeyRepository.php @@ -11,16 +11,14 @@ class ApiKeyRepository extends EloquentRepository implements ApiKeyRepositoryInt { /** * Return the model backing this repository. - * - * @return string */ - public function model() + public function model(): string { return ApiKey::class; } /** - * Get all of the account API keys that exist for a specific user. + * Get all the account API keys that exist for a specific user. */ public function getAccountKeys(User $user): Collection { @@ -30,7 +28,7 @@ public function getAccountKeys(User $user): Collection } /** - * Get all of the application API keys that exist for a specific user. + * Get all the application API keys that exist for a specific user. */ public function getApplicationKeys(User $user): Collection { diff --git a/app/Repositories/Eloquent/BackupRepository.php b/app/Repositories/Eloquent/BackupRepository.php index 051d0ce424..bbc5d2cd95 100644 --- a/app/Repositories/Eloquent/BackupRepository.php +++ b/app/Repositories/Eloquent/BackupRepository.php @@ -5,24 +5,20 @@ use Carbon\Carbon; use Pterodactyl\Models\Backup; use Pterodactyl\Models\Server; +use Illuminate\Support\Collection; use Illuminate\Database\Eloquent\Relations\HasMany; class BackupRepository extends EloquentRepository { - /** - * @return string - */ - public function model() + public function model(): string { return Backup::class; } /** * Determines if too many backups have been generated by the server. - * - * @return \Pterodactyl\Models\Backup[]|\Illuminate\Support\Collection */ - public function getBackupsGeneratedDuringTimespan(int $server, int $seconds = 600) + public function getBackupsGeneratedDuringTimespan(int $server, int $seconds = 600): array|Collection { return $this->getBuilder() ->withTrashed() diff --git a/app/Repositories/Eloquent/DatabaseHostRepository.php b/app/Repositories/Eloquent/DatabaseHostRepository.php index c55b03232d..2c2b9dcde7 100644 --- a/app/Repositories/Eloquent/DatabaseHostRepository.php +++ b/app/Repositories/Eloquent/DatabaseHostRepository.php @@ -10,10 +10,8 @@ class DatabaseHostRepository extends EloquentRepository implements DatabaseHostR { /** * Return the model backing this repository. - * - * @return string */ - public function model() + public function model(): string { return DatabaseHost::class; } diff --git a/app/Repositories/Eloquent/DatabaseRepository.php b/app/Repositories/Eloquent/DatabaseRepository.php index 53e590df9c..9bc33d035e 100644 --- a/app/Repositories/Eloquent/DatabaseRepository.php +++ b/app/Repositories/Eloquent/DatabaseRepository.php @@ -11,58 +11,44 @@ class DatabaseRepository extends EloquentRepository implements DatabaseRepositoryInterface { - /** - * @var string - */ - protected $connection = self::DEFAULT_CONNECTION_NAME; - - /** - * @var \Illuminate\Database\DatabaseManager - */ - protected $database; + protected string $connection = self::DEFAULT_CONNECTION_NAME; /** * DatabaseRepository constructor. */ - public function __construct(Application $application, DatabaseManager $database) + public function __construct(Application $application, private DatabaseManager $database) { parent::__construct($application); - - $this->database = $database; } /** * Return the model backing this repository. - * - * @return string */ - public function model() + public function model(): string { return Database::class; } /** - * Set the connection name to execute statements against. - * - * @return $this + * Return the connection to execute statements against. */ - public function setConnection(string $connection) + public function getConnection(): string { - $this->connection = $connection; - - return $this; + return $this->connection; } /** - * Return the connection to execute statements against. + * Set the connection name to execute statements against. */ - public function getConnection(): string + public function setConnection(string $connection): self { - return $this->connection; + $this->connection = $connection; + + return $this; } /** - * Return all of the databases belonging to a server. + * Return all the databases belonging to a server. */ public function getDatabasesForServer(int $server): Collection { @@ -70,7 +56,7 @@ public function getDatabasesForServer(int $server): Collection } /** - * Return all of the databases for a given host with the server relationship loaded. + * Return all the databases for a given host with the server relationship loaded. */ public function getDatabasesForHost(int $host, int $count = 25): LengthAwarePaginator { @@ -89,16 +75,18 @@ public function createDatabase(string $database): bool /** * Create a new database user on a given connection. - * - * @param $max_connections */ - public function createUser(string $username, string $remote, string $password, $max_connections): bool + public function createUser(string $username, string $remote, string $password, ?int $max_connections): bool { - if (!$max_connections) { - return $this->run(sprintf('CREATE USER `%s`@`%s` IDENTIFIED BY \'%s\'', $username, $remote, $password)); - } else { - return $this->run(sprintf('CREATE USER `%s`@`%s` IDENTIFIED BY \'%s\' WITH MAX_USER_CONNECTIONS %s', $username, $remote, $password, $max_connections)); + $args = [$username, $remote, $password]; + $command = 'CREATE USER `%s`@`%s` IDENTIFIED BY \'%s\''; + + if (!empty($max_connections)) { + $args[] = $max_connections; + $command .= ' WITH MAX_USER_CONNECTIONS %s'; } + + return $this->run(sprintf($command, ...$args)); } /** @@ -132,8 +120,6 @@ public function dropDatabase(string $database): bool /** * Drop a given user on a specific connection. - * - * @return mixed */ public function dropUser(string $username, string $remote): bool { diff --git a/app/Repositories/Eloquent/EggRepository.php b/app/Repositories/Eloquent/EggRepository.php index 98b7db4530..8c4a01a270 100644 --- a/app/Repositories/Eloquent/EggRepository.php +++ b/app/Repositories/Eloquent/EggRepository.php @@ -13,10 +13,8 @@ class EggRepository extends EloquentRepository implements EggRepositoryInterface { /** * Return the model backing this repository. - * - * @return string */ - public function model() + public function model(): string { return Egg::class; } @@ -30,7 +28,7 @@ public function getWithVariables(int $id): Egg { try { return $this->getBuilder()->with('variables')->findOrFail($id, $this->getColumns()); - } catch (ModelNotFoundException $exception) { + } catch (ModelNotFoundException) { throw new RecordNotFoundException(); } } @@ -52,17 +50,17 @@ public function getAllWithCopyAttributes(): Collection */ public function getWithCopyAttributes($value, string $column = 'id'): Egg { - Assert::true((is_digit($value) || is_string($value)), 'First argument passed to getWithCopyAttributes must be an integer or string, received %s.'); + Assert::true(is_digit($value) || is_string($value), 'First argument passed to getWithCopyAttributes must be an integer or string, received %s.'); try { return $this->getBuilder()->with('scriptFrom', 'configFrom')->where($column, '=', $value)->firstOrFail($this->getColumns()); - } catch (ModelNotFoundException $exception) { + } catch (ModelNotFoundException) { throw new RecordNotFoundException(); } } /** - * Return all of the data needed to export a service. + * Return all the data needed to export a service. * * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ @@ -70,7 +68,7 @@ public function getWithExportAttributes(int $id): Egg { try { return $this->getBuilder()->with('scriptFrom', 'configFrom', 'variables')->findOrFail($id, $this->getColumns()); - } catch (ModelNotFoundException $exception) { + } catch (ModelNotFoundException) { throw new RecordNotFoundException(); } } diff --git a/app/Repositories/Eloquent/EggVariableRepository.php b/app/Repositories/Eloquent/EggVariableRepository.php index ce79a65f6d..8ca35debbb 100644 --- a/app/Repositories/Eloquent/EggVariableRepository.php +++ b/app/Repositories/Eloquent/EggVariableRepository.php @@ -10,10 +10,8 @@ class EggVariableRepository extends EloquentRepository implements EggVariableRep { /** * Return the model backing this repository. - * - * @return string */ - public function model() + public function model(): string { return EggVariable::class; } diff --git a/app/Repositories/Eloquent/EloquentRepository.php b/app/Repositories/Eloquent/EloquentRepository.php index 6403d27641..a4e7f2fd78 100644 --- a/app/Repositories/Eloquent/EloquentRepository.php +++ b/app/Repositories/Eloquent/EloquentRepository.php @@ -5,6 +5,7 @@ use Illuminate\Http\Request; use Webmozart\Assert\Assert; use Illuminate\Support\Collection; +use Illuminate\Database\Eloquent\Model; use Pterodactyl\Repositories\Repository; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Query\Expression; @@ -16,21 +17,14 @@ abstract class EloquentRepository extends Repository implements RepositoryInterface { - /** - * @var bool - */ - protected $useRequestFilters = false; + protected bool $useRequestFilters = false; /** * Determines if the repository function should use filters off the request object * present when returning results. This allows repository methods to be called in API * context's such that we can pass through ?filter[name]=Dane&sort=desc for example. - * - * @param bool $usingFilters - * - * @return $this */ - public function usingRequestFilters($usingFilters = true) + public function usingRequestFilters(bool $usingFilters = true): self { $this->useRequestFilters = $usingFilters; @@ -39,20 +33,16 @@ public function usingRequestFilters($usingFilters = true) /** * Returns the request instance. - * - * @return \Illuminate\Http\Request */ - protected function request() + protected function request(): Request { return $this->app->make(Request::class); } /** * Paginate the response data based on the page para. - * - * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator */ - protected function paginate(Builder $instance, int $default = 50) + protected function paginate(Builder $instance, int $default = 50): LengthAwarePaginator { if (!$this->useRequestFilters) { return $instance->paginate($default); @@ -64,20 +54,16 @@ protected function paginate(Builder $instance, int $default = 50) /** * Return an instance of the eloquent model bound to this * repository instance. - * - * @return \Illuminate\Database\Eloquent\Model */ - public function getModel() + public function getModel(): Model { return $this->model; } /** * Return an instance of the builder to use for this repository. - * - * @return \Illuminate\Database\Eloquent\Builder */ - public function getBuilder() + public function getBuilder(): Builder { return $this->getModel()->newQuery(); } @@ -85,11 +71,9 @@ public function getBuilder() /** * Create a new record in the database and return the associated model. * - * @return \Illuminate\Database\Eloquent\Model|bool - * * @throws \Pterodactyl\Exceptions\Model\DataValidationException */ - public function create(array $fields, bool $validate = true, bool $force = false) + public function create(array $fields, bool $validate = true, bool $force = false): Model|bool { $instance = $this->getBuilder()->newModelInstance(); ($force) ? $instance->forceFill($fields) : $instance->fill($fields); @@ -108,15 +92,13 @@ public function create(array $fields, bool $validate = true, bool $force = false /** * Find a model that has the specific ID passed. * - * @return \Illuminate\Database\Eloquent\Model - * * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function find(int $id) + public function find(int $id): Model { try { return $this->getBuilder()->findOrFail($id, $this->getColumns()); - } catch (ModelNotFoundException $exception) { + } catch (ModelNotFoundException) { throw new RecordNotFoundException(); } } @@ -132,15 +114,13 @@ public function findWhere(array $fields): Collection /** * Find and return the first matching instance for the given fields. * - * @return \Illuminate\Database\Eloquent\Model - * * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function findFirstWhere(array $fields) + public function findFirstWhere(array $fields): Model { try { return $this->getBuilder()->where($fields)->firstOrFail($this->getColumns()); - } catch (ModelNotFoundException $exception) { + } catch (ModelNotFoundException) { throw new RecordNotFoundException(); } } @@ -174,18 +154,14 @@ public function deleteWhere(array $attributes, bool $force = false): int /** * Update a given ID with the passed array of fields. * - * @param int $id - * - * @return \Illuminate\Database\Eloquent\Model|bool - * * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function update($id, array $fields, bool $validate = true, bool $force = false) + public function update(int $id, array $fields, bool $validate = true, bool $force = false): Model|bool { try { $instance = $this->getBuilder()->where('id', $id)->firstOrFail(); - } catch (ModelNotFoundException $exception) { + } catch (ModelNotFoundException) { throw new RecordNotFoundException(); } @@ -204,12 +180,8 @@ public function update($id, array $fields, bool $validate = true, bool $force = /** * Update a model using the attributes passed. - * - * @param array|\Closure $attributes - * - * @return int */ - public function updateWhere($attributes, array $values) + public function updateWhere(array $attributes, array $values): int { return $this->getBuilder()->where($attributes)->update($values); } @@ -228,12 +200,10 @@ public function updateWhereIn(string $column, array $values, array $fields): int /** * Update a record if it exists in the database, otherwise create it. * - * @return \Illuminate\Database\Eloquent\Model - * * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function updateOrCreate(array $where, array $fields, bool $validate = true, bool $force = false) + public function updateOrCreate(array $where, array $fields, bool $validate = true, bool $force = false): Model|bool { foreach ($where as $item) { Assert::true(is_scalar($item) || is_null($item), 'First argument passed to updateOrCreate should be an array of scalar or null values, received an array value of %s.'); @@ -241,7 +211,7 @@ public function updateOrCreate(array $where, array $fields, bool $validate = tru try { $instance = $this->setColumns('id')->findFirstWhere($where); - } catch (RecordNotFoundException $exception) { + } catch (RecordNotFoundException) { return $this->create(array_merge($where, $fields), $validate, $force); } diff --git a/app/Repositories/Eloquent/LocationRepository.php b/app/Repositories/Eloquent/LocationRepository.php index c06d10a9f0..e25737cb36 100644 --- a/app/Repositories/Eloquent/LocationRepository.php +++ b/app/Repositories/Eloquent/LocationRepository.php @@ -12,10 +12,8 @@ class LocationRepository extends EloquentRepository implements LocationRepositor { /** * Return the model backing this repository. - * - * @return string */ - public function model() + public function model(): string { return Location::class; } @@ -29,7 +27,7 @@ public function getAllWithDetails(): Collection } /** - * Return all of the available locations with the nodes as a relationship. + * Return all the available locations with the nodes as a relationship. */ public function getAllWithNodes(): Collection { @@ -37,9 +35,7 @@ public function getAllWithNodes(): Collection } /** - * Return all of the nodes and their respective count of servers for a location. - * - * @return mixed + * Return all the nodes and their respective count of servers for a location. * * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ @@ -47,7 +43,7 @@ public function getWithNodes(int $id): Location { try { return $this->getBuilder()->with('nodes.servers')->findOrFail($id, $this->getColumns()); - } catch (ModelNotFoundException $exception) { + } catch (ModelNotFoundException) { throw new RecordNotFoundException(); } } @@ -55,15 +51,13 @@ public function getWithNodes(int $id): Location /** * Return a location and the count of nodes in that location. * - * @return mixed - * * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ public function getWithNodeCount(int $id): Location { try { return $this->getBuilder()->withCount('nodes')->findOrFail($id, $this->getColumns()); - } catch (ModelNotFoundException $exception) { + } catch (ModelNotFoundException) { throw new RecordNotFoundException(); } } diff --git a/app/Repositories/Eloquent/MountRepository.php b/app/Repositories/Eloquent/MountRepository.php index 490f76b649..b33e6cb249 100644 --- a/app/Repositories/Eloquent/MountRepository.php +++ b/app/Repositories/Eloquent/MountRepository.php @@ -12,10 +12,8 @@ class MountRepository extends EloquentRepository { /** * Return the model backing this repository. - * - * @return string */ - public function model() + public function model(): string { return Mount::class; } @@ -29,9 +27,7 @@ public function getAllWithDetails(): Collection } /** - * Return all of the mounts and their respective relations. - * - * @return mixed + * Return all the mounts and their respective relations. * * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ diff --git a/app/Repositories/Eloquent/NestRepository.php b/app/Repositories/Eloquent/NestRepository.php index 6d4992e454..f472e4b808 100644 --- a/app/Repositories/Eloquent/NestRepository.php +++ b/app/Repositories/Eloquent/NestRepository.php @@ -1,15 +1,9 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Repositories\Eloquent; use Pterodactyl\Models\Nest; +use Illuminate\Database\Eloquent\Collection; use Pterodactyl\Contracts\Repository\NestRepositoryInterface; use Pterodactyl\Exceptions\Repository\RecordNotFoundException; @@ -17,10 +11,8 @@ class NestRepository extends EloquentRepository implements NestRepositoryInterfa { /** * Return the model backing this repository. - * - * @return string */ - public function model() + public function model(): string { return Nest::class; } @@ -28,13 +20,9 @@ public function model() /** * Return a nest or all nests with their associated eggs and variables. * - * @param int $id - * - * @return \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\Nest - * * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function getWithEggs(int $id = null) + public function getWithEggs(int $id = null): Collection|Nest { $instance = $this->getBuilder()->with('eggs', 'eggs.variables'); @@ -53,11 +41,9 @@ public function getWithEggs(int $id = null) /** * Return a nest or all nests and the count of eggs and servers for that nest. * - * @return \Pterodactyl\Models\Nest|\Illuminate\Database\Eloquent\Collection - * * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function getWithCounts(int $id = null) + public function getWithCounts(int $id = null): Collection|Nest { $instance = $this->getBuilder()->withCount(['eggs', 'servers']); diff --git a/app/Repositories/Eloquent/NodeRepository.php b/app/Repositories/Eloquent/NodeRepository.php index 5d1a8bcac9..fe019e50a9 100644 --- a/app/Repositories/Eloquent/NodeRepository.php +++ b/app/Repositories/Eloquent/NodeRepository.php @@ -10,10 +10,8 @@ class NodeRepository extends EloquentRepository implements NodeRepositoryInterfa { /** * Return the model backing this repository. - * - * @return string */ - public function model() + public function model(): string { return Node::class; } @@ -105,7 +103,7 @@ public function loadNodeAllocations(Node $node, bool $refresh = false): Node $node->allocations() ->orderByRaw('server_id IS NOT NULL DESC, server_id IS NULL') ->orderByRaw('INET_ATON(ip) ASC') - ->orderBy('port', 'asc') + ->orderBy('port') ->with('server:id,name') ->paginate(50) ); diff --git a/app/Repositories/Eloquent/PermissionRepository.php b/app/Repositories/Eloquent/PermissionRepository.php index bcd9714089..780ff34392 100644 --- a/app/Repositories/Eloquent/PermissionRepository.php +++ b/app/Repositories/Eloquent/PermissionRepository.php @@ -10,11 +10,9 @@ class PermissionRepository extends EloquentRepository implements PermissionRepos /** * Return the model backing this repository. * - * @return string - * * @throws \Exception */ - public function model() + public function model(): string { throw new Exception('This functionality is not implemented.'); } diff --git a/app/Repositories/Eloquent/RecoveryTokenRepository.php b/app/Repositories/Eloquent/RecoveryTokenRepository.php index 5dfeeacfe2..5d4dc48501 100644 --- a/app/Repositories/Eloquent/RecoveryTokenRepository.php +++ b/app/Repositories/Eloquent/RecoveryTokenRepository.php @@ -6,10 +6,7 @@ class RecoveryTokenRepository extends EloquentRepository { - /** - * @return string - */ - public function model() + public function model(): string { return RecoveryToken::class; } diff --git a/app/Repositories/Eloquent/ScheduleRepository.php b/app/Repositories/Eloquent/ScheduleRepository.php index 5c999df87c..145333f45f 100644 --- a/app/Repositories/Eloquent/ScheduleRepository.php +++ b/app/Repositories/Eloquent/ScheduleRepository.php @@ -12,16 +12,14 @@ class ScheduleRepository extends EloquentRepository implements ScheduleRepositor { /** * Return the model backing this repository. - * - * @return string */ - public function model() + public function model(): string { return Schedule::class; } /** - * Return all of the schedules for a given server. + * Return all the schedules for a given server. */ public function findServerSchedules(int $server): Collection { @@ -29,7 +27,7 @@ public function findServerSchedules(int $server): Collection } /** - * Return a schedule model with all of the associated tasks as a relationship. + * Return a schedule model with all the associated tasks as a relationship. * * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ @@ -37,7 +35,7 @@ public function getScheduleWithTasks(int $schedule): Schedule { try { return $this->getBuilder()->with('tasks')->findOrFail($schedule, $this->getColumns()); - } catch (ModelNotFoundException $exception) { + } catch (ModelNotFoundException) { throw new RecordNotFoundException(); } } diff --git a/app/Repositories/Eloquent/ServerRepository.php b/app/Repositories/Eloquent/ServerRepository.php index 8bb79c10f5..a353dba466 100644 --- a/app/Repositories/Eloquent/ServerRepository.php +++ b/app/Repositories/Eloquent/ServerRepository.php @@ -14,10 +14,8 @@ class ServerRepository extends EloquentRepository implements ServerRepositoryInt { /** * Return the model backing this repository. - * - * @return string */ - public function model() + public function model(): string { return Server::class; } @@ -77,7 +75,7 @@ public function findWithVariables(int $id): Server return $this->getBuilder()->with('egg.variables', 'variables') ->where($this->getModel()->getKeyName(), '=', $id) ->firstOrFail($this->getColumns()); - } catch (ModelNotFoundException $exception) { + } catch (ModelNotFoundException) { throw new RecordNotFoundException(); } } @@ -155,7 +153,7 @@ public function getByUuid(string $uuid): Server ->firstOrFail($this->getColumns()); return $model; - } catch (ModelNotFoundException $exception) { + } catch (ModelNotFoundException) { throw new RecordNotFoundException(); } } @@ -169,7 +167,7 @@ public function isUniqueUuidCombo(string $uuid, string $short): bool } /** - * Returns all of the servers that exist for a given node in a paginated response. + * Returns all the servers that exist for a given node in a paginated response. */ public function loadAllServersForNode(int $node, int $limit): LengthAwarePaginator { diff --git a/app/Repositories/Eloquent/ServerVariableRepository.php b/app/Repositories/Eloquent/ServerVariableRepository.php index d0d5e4dba1..26ded2cf64 100644 --- a/app/Repositories/Eloquent/ServerVariableRepository.php +++ b/app/Repositories/Eloquent/ServerVariableRepository.php @@ -9,10 +9,8 @@ class ServerVariableRepository extends EloquentRepository implements ServerVaria { /** * Return the model backing this repository. - * - * @return string */ - public function model() + public function model(): string { return ServerVariable::class; } diff --git a/app/Repositories/Eloquent/SessionRepository.php b/app/Repositories/Eloquent/SessionRepository.php index ad069abb82..5aa355dc04 100644 --- a/app/Repositories/Eloquent/SessionRepository.php +++ b/app/Repositories/Eloquent/SessionRepository.php @@ -10,16 +10,14 @@ class SessionRepository extends EloquentRepository implements SessionRepositoryI { /** * Return the model backing this repository. - * - * @return string */ - public function model() + public function model(): string { return Session::class; } /** - * Return all of the active sessions for a user. + * Return all the active sessions for a user. */ public function getUserSessions(int $user): Collection { @@ -28,10 +26,8 @@ public function getUserSessions(int $user): Collection /** * Delete a session for a given user. - * - * @return int|null */ - public function deleteUserSession(int $user, string $session) + public function deleteUserSession(int $user, string $session): ?int { return $this->getBuilder()->where('user_id', $user)->where('id', $session)->delete(); } diff --git a/app/Repositories/Eloquent/SettingsRepository.php b/app/Repositories/Eloquent/SettingsRepository.php index b39d3a2e14..df22fce595 100644 --- a/app/Repositories/Eloquent/SettingsRepository.php +++ b/app/Repositories/Eloquent/SettingsRepository.php @@ -7,22 +7,14 @@ class SettingsRepository extends EloquentRepository implements SettingsRepositoryInterface { - /** - * @var array - */ - private static $cache = []; + private static array $cache = []; - /** - * @var array - */ - private static $databaseMiss = []; + private static array $databaseMiss = []; /** * Return the model backing this repository. - * - * @return string */ - public function model() + public function model(): string { return Setting::class; } @@ -31,7 +23,6 @@ public function model() * Store a new persistent setting in the database. * * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ public function set(string $key, string $value = null) { @@ -44,12 +35,8 @@ public function set(string $key, string $value = null) /** * Retrieve a persistent setting from the database. - * - * @param mixed $default - * - * @return mixed */ - public function get(string $key, $default = null) + public function get(string $key, mixed $default = null): mixed { // If item has already been requested return it from the cache. If // we already know it is missing, immediately return the default value. diff --git a/app/Repositories/Eloquent/SubuserRepository.php b/app/Repositories/Eloquent/SubuserRepository.php index d40ed9fda3..141378753d 100644 --- a/app/Repositories/Eloquent/SubuserRepository.php +++ b/app/Repositories/Eloquent/SubuserRepository.php @@ -10,10 +10,8 @@ class SubuserRepository extends EloquentRepository implements SubuserRepositoryI { /** * Return the model backing this repository. - * - * @return string */ - public function model() + public function model(): string { return Subuser::class; } diff --git a/app/Repositories/Eloquent/TaskRepository.php b/app/Repositories/Eloquent/TaskRepository.php index a375939adb..942c54d479 100644 --- a/app/Repositories/Eloquent/TaskRepository.php +++ b/app/Repositories/Eloquent/TaskRepository.php @@ -11,10 +11,8 @@ class TaskRepository extends EloquentRepository implements TaskRepositoryInterfa { /** * Return the model backing this repository. - * - * @return string */ - public function model() + public function model(): string { return Task::class; } @@ -28,20 +26,18 @@ public function getTaskForJobProcess(int $id): Task { try { return $this->getBuilder()->with('server.user', 'schedule')->findOrFail($id, $this->getColumns()); - } catch (ModelNotFoundException $exception) { + } catch (ModelNotFoundException) { throw new RecordNotFoundException(); } } /** * Returns the next task in a schedule. - * - * @return \Pterodactyl\Models\Task|null */ - public function getNextTask(int $schedule, int $index) + public function getNextTask(int $schedule, int $index): ?Task { return $this->getBuilder()->where('schedule_id', '=', $schedule) - ->orderBy('sequence_id', 'asc') + ->orderBy('sequence_id') ->where('sequence_id', '>', $index) ->first($this->getColumns()); } diff --git a/app/Repositories/Eloquent/UserRepository.php b/app/Repositories/Eloquent/UserRepository.php index 72a88efb01..4445346251 100644 --- a/app/Repositories/Eloquent/UserRepository.php +++ b/app/Repositories/Eloquent/UserRepository.php @@ -9,10 +9,8 @@ class UserRepository extends EloquentRepository implements UserRepositoryInterfa { /** * Return the model backing this repository. - * - * @return string */ - public function model() + public function model(): string { return User::class; } diff --git a/app/Repositories/Repository.php b/app/Repositories/Repository.php index a800519429..00683f6d77 100644 --- a/app/Repositories/Repository.php +++ b/app/Repositories/Repository.php @@ -4,53 +4,34 @@ use InvalidArgumentException; use Illuminate\Foundation\Application; +use Illuminate\Database\Eloquent\Model; use Pterodactyl\Contracts\Repository\RepositoryInterface; abstract class Repository implements RepositoryInterface { - /** - * @var \Illuminate\Foundation\Application - */ - protected $app; - - /** - * @var array - */ - protected $columns = ['*']; + protected array $columns = ['*']; - /** - * @var mixed - */ - protected $model; + protected Model $model; - /** - * @var bool - */ - protected $withFresh = true; + protected bool $withFresh = true; /** * Repository constructor. */ - public function __construct(Application $application) + public function __construct(protected Application $app) { - $this->app = $application; - $this->initializeModel($this->model()); } /** * Return the model backing this repository. - * - * @return string|\Closure|object */ - abstract public function model(); + abstract public function model(): string; /** * Return the model being used for this repository. - * - * @return mixed */ - public function getModel() + public function getModel(): Model { return $this->model; } @@ -59,10 +40,8 @@ public function getModel() * Setup column selection functionality. * * @param array|string $columns - * - * @return $this */ - public function setColumns($columns = ['*']) + public function setColumns($columns = ['*']): self { $clone = clone $this; $clone->columns = is_array($columns) ? $columns : func_get_args(); @@ -72,10 +51,8 @@ public function setColumns($columns = ['*']) /** * Return the columns to be selected in the repository call. - * - * @return array */ - public function getColumns() + public function getColumns(): array { return $this->columns; } @@ -83,31 +60,25 @@ public function getColumns() /** * Stop repository update functions from returning a fresh * model when changes are committed. - * - * @return $this */ - public function withoutFreshModel() + public function withoutFreshModel(): self { return $this->setFreshModel(false); } /** * Return a fresh model with a repository updates a model. - * - * @return $this */ - public function withFreshModel() + public function withFreshModel(): self { - return $this->setFreshModel(true); + return $this->setFreshModel(); } /** - * Set whether or not the repository should return a fresh model + * Set whether the repository should return a fresh model * when changes are committed. - * - * @return $this */ - public function setFreshModel(bool $fresh = true) + public function setFreshModel(bool $fresh = true): self { $clone = clone $this; $clone->withFresh = $fresh; @@ -117,12 +88,8 @@ public function setFreshModel(bool $fresh = true) /** * Take the provided model and make it accessible to the rest of the repository. - * - * @param array $model - * - * @return mixed */ - protected function initializeModel(...$model) + protected function initializeModel(string ...$model): mixed { switch (count($model)) { case 1: diff --git a/app/Repositories/Wings/DaemonBackupRepository.php b/app/Repositories/Wings/DaemonBackupRepository.php index 571775fa52..4c164ee6b3 100644 --- a/app/Repositories/Wings/DaemonBackupRepository.php +++ b/app/Repositories/Wings/DaemonBackupRepository.php @@ -11,17 +11,12 @@ class DaemonBackupRepository extends DaemonRepository { - /** - * @var string|null - */ - protected $adapter; + protected ?string $adapter; /** * Sets the backup adapter for this execution instance. - * - * @return $this */ - public function setBackupAdapter(string $adapter) + public function setBackupAdapter(string $adapter): self { $this->adapter = $adapter; diff --git a/app/Repositories/Wings/DaemonCommandRepository.php b/app/Repositories/Wings/DaemonCommandRepository.php index 390434bff6..cde29ff366 100644 --- a/app/Repositories/Wings/DaemonCommandRepository.php +++ b/app/Repositories/Wings/DaemonCommandRepository.php @@ -13,11 +13,9 @@ class DaemonCommandRepository extends DaemonRepository /** * Sends a command or multiple commands to a running server instance. * - * @param string|string[] $command - * * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException */ - public function send($command): ResponseInterface + public function send(array|string $command): ResponseInterface { Assert::isInstanceOf($this->server, Server::class); diff --git a/app/Repositories/Wings/DaemonConfigurationRepository.php b/app/Repositories/Wings/DaemonConfigurationRepository.php index f2672ddfa6..d24fb7e501 100644 --- a/app/Repositories/Wings/DaemonConfigurationRepository.php +++ b/app/Repositories/Wings/DaemonConfigurationRepository.php @@ -3,6 +3,7 @@ namespace Pterodactyl\Repositories\Wings; use Pterodactyl\Models\Node; +use Psr\Http\Message\ResponseInterface; use GuzzleHttp\Exception\TransferException; use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException; @@ -29,11 +30,9 @@ public function getSystemInformation(): array * this instance using a passed-in model. This allows us to change plenty of information * in the model, and still use the old, pre-update model to actually make the HTTP request. * - * @return \Psr\Http\Message\ResponseInterface - * * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException */ - public function update(Node $node) + public function update(Node $node): ResponseInterface { try { return $this->getHttpClient()->post( diff --git a/app/Repositories/Wings/DaemonRepository.php b/app/Repositories/Wings/DaemonRepository.php index 268a4a2131..512626d73f 100644 --- a/app/Repositories/Wings/DaemonRepository.php +++ b/app/Repositories/Wings/DaemonRepository.php @@ -10,35 +10,21 @@ abstract class DaemonRepository { - /** - * @var \Illuminate\Contracts\Foundation\Application - */ - protected $app; + protected ?Server $server; - /** - * @var \Pterodactyl\Models\Server|null - */ - protected $server; - - /** - * @var \Pterodactyl\Models\Node|null - */ - protected $node; + protected ?Node $node; /** - * BaseWingsRepository constructor. + * DaemonRepository constructor. */ - public function __construct(Application $application) + public function __construct(protected Application $app) { - $this->app = $application; } /** * Set the server model this request is stemming from. - * - * @return $this */ - public function setServer(Server $server) + public function setServer(Server $server): self { $this->server = $server; @@ -49,10 +35,8 @@ public function setServer(Server $server) /** * Set the node model this request is stemming from. - * - * @return $this */ - public function setNode(Node $node) + public function setNode(Node $node): self { $this->node = $node; diff --git a/app/Rules/Fqdn.php b/app/Rules/Fqdn.php index 4f4a04dacf..47baf510d7 100644 --- a/app/Rules/Fqdn.php +++ b/app/Rules/Fqdn.php @@ -28,9 +28,8 @@ public function setData($data): self * * @param string $attribute * @param mixed $value - * @return bool */ - public function passes($attribute, $value) + public function passes($attribute, $value): bool { if (filter_var($value, FILTER_VALIDATE_IP)) { // Check if the scheme is set to HTTPS. diff --git a/app/Rules/Username.php b/app/Rules/Username.php index bae204952b..b89184e9ab 100644 --- a/app/Rules/Username.php +++ b/app/Rules/Username.php @@ -13,7 +13,7 @@ class Username implements Rule /** * Validate that a username contains only the allowed characters and starts/ends - * with alpha-numeric characters. + * with alphanumeric characters. * * Allowed characters: a-z0-9_-. * @@ -37,10 +37,8 @@ public function message(): string /** * Convert the rule to a validation string. This is necessary to avoid * issues with Eloquence which tries to use this rule as a string. - * - * @return string */ - public function __toString() + public function __toString(): string { return 'p_username'; } diff --git a/app/Services/Acl/Api/AdminAcl.php b/app/Services/Acl/Api/AdminAcl.php index 880794adca..59c910c191 100644 --- a/app/Services/Acl/Api/AdminAcl.php +++ b/app/Services/Acl/Api/AdminAcl.php @@ -37,10 +37,8 @@ class AdminAcl /** * Determine if an API key has permission to perform a specific read/write operation. - * - * @return bool */ - public static function can(int $permission, int $action = self::READ) + public static function can(int $permission, int $action = self::READ): bool { if ($permission & $action) { return true; @@ -52,10 +50,8 @@ public static function can(int $permission, int $action = self::READ) /** * Determine if an API Key model has permission to access a given resource * at a specific action level. - * - * @return bool */ - public static function check(ApiKey $key, string $resource, int $action = self::READ) + public static function check(ApiKey $key, string $resource, int $action = self::READ): bool { return self::can(data_get($key, self::COLUMN_IDENTIFIER . $resource, self::NONE), $action); } diff --git a/app/Services/Activity/AcitvityLogBatchService.php b/app/Services/Activity/ActivityLogBatchService.php similarity index 91% rename from app/Services/Activity/AcitvityLogBatchService.php rename to app/Services/Activity/ActivityLogBatchService.php index 8e2d142f09..f4206ea569 100644 --- a/app/Services/Activity/AcitvityLogBatchService.php +++ b/app/Services/Activity/ActivityLogBatchService.php @@ -4,7 +4,7 @@ use Ramsey\Uuid\Uuid; -class AcitvityLogBatchService +class ActivityLogBatchService { protected int $transaction = 0; protected ?string $uuid = null; @@ -47,10 +47,8 @@ public function end(): void /** * Executes the logic provided within the callback in the scope of an activity * log batch transaction. - * - * @return mixed */ - public function transaction(\Closure $callback) + public function transaction(\Closure $callback): mixed { $this->start(); $result = $callback($this->uuid()); diff --git a/app/Services/Activity/ActivityLogService.php b/app/Services/Activity/ActivityLogService.php index cfdd5a9cde..fa4f469369 100644 --- a/app/Services/Activity/ActivityLogService.php +++ b/app/Services/Activity/ActivityLogService.php @@ -7,11 +7,11 @@ use Illuminate\Support\Collection; use Illuminate\Support\Facades\Log; use Pterodactyl\Models\ActivityLog; -use Illuminate\Contracts\Auth\Factory; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Facades\Request; use Pterodactyl\Models\ActivityLogSubject; use Illuminate\Database\ConnectionInterface; +use Illuminate\Contracts\Auth\Factory as AuthFactory; class ActivityLogService { @@ -19,24 +19,12 @@ class ActivityLogService protected array $subjects = []; - protected Factory $manager; - - protected ConnectionInterface $connection; - - protected AcitvityLogBatchService $batch; - - protected ActivityLogTargetableService $targetable; - public function __construct( - Factory $manager, - AcitvityLogBatchService $batch, - ActivityLogTargetableService $targetable, - ConnectionInterface $connection + protected AuthFactory $manager, + protected ActivityLogBatchService $batch, + protected ActivityLogTargetableService $targetable, + protected ConnectionInterface $connection ) { - $this->manager = $manager; - $this->batch = $batch; - $this->targetable = $targetable; - $this->connection = $connection; } /** @@ -75,11 +63,17 @@ public function description(?string $description): self /** * Sets the subject model instance. * - * @param \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Model[] $subjects + * @template T extends \Illuminate\Database\Eloquent\Model|\Illuminate\Contracts\Auth\Authenticatable + * + * @param T|T[]|null $subjects */ public function subject(...$subjects): self { foreach (Arr::wrap($subjects) as $subject) { + if (is_null($subject)) { + continue; + } + foreach ($this->subjects as $entry) { // If this subject is already tracked in our array of subjects just skip over // it and move on to the next one in the list. @@ -170,11 +164,9 @@ public function clone(): self /** * Executes the provided callback within the scope of a database transaction - * and will only save the activity log entry if everything else succesfully + * and will only save the activity log entry if everything else successfully * settles. * - * @return mixed - * * @throws \Throwable */ public function transaction(\Closure $callback) diff --git a/app/Services/Allocations/AllocationDeletionService.php b/app/Services/Allocations/AllocationDeletionService.php index 4b1bfec3b2..8ea677245e 100644 --- a/app/Services/Allocations/AllocationDeletionService.php +++ b/app/Services/Allocations/AllocationDeletionService.php @@ -8,28 +8,20 @@ class AllocationDeletionService { - /** - * @var \Pterodactyl\Contracts\Repository\AllocationRepositoryInterface - */ - private $repository; - /** * AllocationDeletionService constructor. */ - public function __construct(AllocationRepositoryInterface $repository) + public function __construct(private AllocationRepositoryInterface $repository) { - $this->repository = $repository; } /** * Delete an allocation from the database only if it does not have a server * that is actively attached to it. * - * @return int - * * @throws \Pterodactyl\Exceptions\Service\Allocation\ServerUsingAllocationException */ - public function handle(Allocation $allocation) + public function handle(Allocation $allocation): int { if (!is_null($allocation->server_id)) { throw new ServerUsingAllocationException(trans('exceptions.allocations.server_using')); diff --git a/app/Services/Allocations/AssignmentService.php b/app/Services/Allocations/AssignmentService.php index a7563eb28f..ec79d18f18 100644 --- a/app/Services/Allocations/AssignmentService.php +++ b/app/Services/Allocations/AssignmentService.php @@ -22,23 +22,11 @@ class AssignmentService public const PORT_RANGE_LIMIT = 1000; public const PORT_RANGE_REGEX = '/^(\d{4,5})-(\d{4,5})$/'; - /** - * @var \Illuminate\Database\ConnectionInterface - */ - protected $connection; - - /** - * @var \Pterodactyl\Contracts\Repository\AllocationRepositoryInterface - */ - protected $repository; - /** * AssignmentService constructor. */ - public function __construct(AllocationRepositoryInterface $repository, ConnectionInterface $connection) + public function __construct(protected AllocationRepositoryInterface $repository, protected ConnectionInterface $connection) { - $this->connection = $connection; - $this->repository = $repository; } /** @@ -50,7 +38,7 @@ public function __construct(AllocationRepositoryInterface $repository, Connectio * @throws \Pterodactyl\Exceptions\Service\Allocation\PortOutOfRangeException * @throws \Pterodactyl\Exceptions\Service\Allocation\TooManyPortsInRangeException */ - public function handle(Node $node, array $data) + public function handle(Node $node, array $data): void { $explode = explode('/', $data['allocation_ip']); if (count($explode) !== 1) { diff --git a/app/Services/Allocations/FindAssignableAllocationService.php b/app/Services/Allocations/FindAssignableAllocationService.php index 29c37123f8..3374fa07ee 100644 --- a/app/Services/Allocations/FindAssignableAllocationService.php +++ b/app/Services/Allocations/FindAssignableAllocationService.php @@ -10,19 +10,11 @@ class FindAssignableAllocationService { - /** - * @var \Pterodactyl\Services\Allocations\AssignmentService - */ - private $service; - /** * FindAssignableAllocationService constructor. - * - * @param \Pterodactyl\Services\Allocations\AssignmentService $service */ - public function __construct(AssignmentService $service) + public function __construct(private AssignmentService $service) { - $this->service = $service; } /** @@ -30,15 +22,13 @@ public function __construct(AssignmentService $service) * no allocation can be found, a new one will be created with a random port between the defined * range from the configuration. * - * @return \Pterodactyl\Models\Allocation - * * @throws \Pterodactyl\Exceptions\DisplayException * @throws \Pterodactyl\Exceptions\Service\Allocation\CidrOutOfRangeException * @throws \Pterodactyl\Exceptions\Service\Allocation\InvalidPortMappingException * @throws \Pterodactyl\Exceptions\Service\Allocation\PortOutOfRangeException * @throws \Pterodactyl\Exceptions\Service\Allocation\TooManyPortsInRangeException */ - public function handle(Server $server) + public function handle(Server $server): Allocation { if (!config('pterodactyl.client_features.allocations.enabled')) { throw new AutoAllocationNotEnabledException(); diff --git a/app/Services/Api/KeyCreationService.php b/app/Services/Api/KeyCreationService.php index 29f079c81b..f026b9f221 100644 --- a/app/Services/Api/KeyCreationService.php +++ b/app/Services/Api/KeyCreationService.php @@ -8,37 +8,20 @@ class KeyCreationService { - /** - * @var \Illuminate\Contracts\Encryption\Encrypter - */ - private $encrypter; - - /** - * @var int - */ - private $keyType = ApiKey::TYPE_NONE; - - /** - * @var \Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface - */ - private $repository; + private int $keyType = ApiKey::TYPE_NONE; /** * ApiKeyService constructor. */ - public function __construct(ApiKeyRepositoryInterface $repository, Encrypter $encrypter) + public function __construct(private ApiKeyRepositoryInterface $repository, private Encrypter $encrypter) { - $this->encrypter = $encrypter; - $this->repository = $repository; } /** * Set the type of key that should be created. By default an orphaned key will be * created. These keys cannot be used for anything, and will not render in the UI. - * - * @return \Pterodactyl\Services\Api\KeyCreationService */ - public function setKeyType(int $type) + public function setKeyType(int $type): self { $this->keyType = $type; diff --git a/app/Services/Backups/DeleteBackupService.php b/app/Services/Backups/DeleteBackupService.php index 66eefe6755..fd65969c17 100644 --- a/app/Services/Backups/DeleteBackupService.php +++ b/app/Services/Backups/DeleteBackupService.php @@ -7,46 +7,17 @@ use GuzzleHttp\Exception\ClientException; 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 { - /** - * @var \Pterodactyl\Repositories\Eloquent\BackupRepository - */ - private $repository; - - /** - * @var \Pterodactyl\Repositories\Wings\DaemonBackupRepository - */ - private $daemonBackupRepository; - - /** - * @var \Illuminate\Database\ConnectionInterface - */ - private $connection; - - /** - * @var \Pterodactyl\Extensions\Backups\BackupManager - */ - private $manager; - - /** - * DeleteBackupService constructor. - */ public function __construct( - ConnectionInterface $connection, - BackupRepository $repository, - BackupManager $manager, - DaemonBackupRepository $daemonBackupRepository + private ConnectionInterface $connection, + private BackupManager $manager, + private DaemonBackupRepository $daemonBackupRepository ) { - $this->repository = $repository; - $this->daemonBackupRepository = $daemonBackupRepository; - $this->connection = $connection; - $this->manager = $manager; } /** @@ -55,7 +26,7 @@ public function __construct( * * @throws \Throwable */ - public function handle(Backup $backup) + public function handle(Backup $backup): void { // If the backup is marked as failed it can still be deleted, even if locked // since the UI doesn't allow you to unlock a failed backup in the first place. @@ -79,13 +50,13 @@ public function handle(Backup $backup) } catch (DaemonConnectionException $exception) { $previous = $exception->getPrevious(); // Don't fail the request if the Daemon responds with a 404, just assume the backup - // doesn't actually exist and remove it's reference from the Panel as well. + // doesn't actually exist and remove its reference from the Panel as well. if (!$previous instanceof ClientException || $previous->getResponse()->getStatusCode() !== Response::HTTP_NOT_FOUND) { throw $exception; } } - $this->repository->delete($backup->id); + $backup->delete(); }); } @@ -94,12 +65,12 @@ public function handle(Backup $backup) * * @throws \Throwable */ - protected function deleteFromS3(Backup $backup) + protected function deleteFromS3(Backup $backup): void { $this->connection->transaction(function () use ($backup) { - $this->repository->delete($backup->id); + $backup->delete(); - /** @var \League\Flysystem\AwsS3v3\AwsS3Adapter $adapter */ + /** @var \Pterodactyl\Extensions\Filesystem\S3Filesystem $adapter */ $adapter = $this->manager->adapter(Backup::ADAPTER_AWS_S3); $adapter->getClient()->deleteObject([ diff --git a/app/Services/Backups/DownloadLinkService.php b/app/Services/Backups/DownloadLinkService.php index 1a3812968f..f3f76c8451 100644 --- a/app/Services/Backups/DownloadLinkService.php +++ b/app/Services/Backups/DownloadLinkService.php @@ -10,23 +10,11 @@ class DownloadLinkService { - /** - * @var \Pterodactyl\Extensions\Backups\BackupManager - */ - private $backupManager; - - /** - * @var \Pterodactyl\Services\Nodes\NodeJWTService - */ - private $jwtService; - /** * DownloadLinkService constructor. */ - public function __construct(BackupManager $backupManager, NodeJWTService $jwtService) + public function __construct(private BackupManager $backupManager, private NodeJWTService $jwtService) { - $this->backupManager = $backupManager; - $this->jwtService = $jwtService; } /** @@ -54,12 +42,10 @@ public function handle(Backup $backup, User $user): string /** * Returns a signed URL that allows us to download a file directly out of a non-public * S3 bucket by using a signed URL. - * - * @return string */ - protected function getS3BackupUrl(Backup $backup) + protected function getS3BackupUrl(Backup $backup): string { - /** @var \League\Flysystem\AwsS3v3\AwsS3Adapter $adapter */ + /** @var \Pterodactyl\Extensions\Filesystem\S3Filesystem $adapter */ $adapter = $this->backupManager->adapter(Backup::ADAPTER_AWS_S3); $request = $adapter->getClient()->createPresignedRequest( diff --git a/app/Services/Backups/InitiateBackupService.php b/app/Services/Backups/InitiateBackupService.php index 705bdf4280..be8f966326 100644 --- a/app/Services/Backups/InitiateBackupService.php +++ b/app/Services/Backups/InitiateBackupService.php @@ -16,65 +16,25 @@ class InitiateBackupService { - /** - * @var string[]|null - */ - private $ignoredFiles; - - /** - * @var bool - */ - private $isLocked = false; - - /** - * @var \Pterodactyl\Repositories\Eloquent\BackupRepository - */ - private $repository; - - /** - * @var \Illuminate\Database\ConnectionInterface - */ - private $connection; + private ?array $ignoredFiles; - /** - * @var \Pterodactyl\Repositories\Wings\DaemonBackupRepository - */ - private $daemonBackupRepository; - - /** - * @var \Pterodactyl\Extensions\Backups\BackupManager - */ - private $backupManager; - - /** - * @var \Pterodactyl\Services\Backups\DeleteBackupService - */ - private $deleteBackupService; + private bool $isLocked = false; /** * InitiateBackupService constructor. - * - * @param \Pterodactyl\Services\Backups\DeleteBackupService $deleteBackupService */ public function __construct( - BackupRepository $repository, - ConnectionInterface $connection, - DaemonBackupRepository $daemonBackupRepository, - DeleteBackupService $deleteBackupService, - BackupManager $backupManager + private BackupRepository $repository, + private ConnectionInterface $connection, + private DaemonBackupRepository $daemonBackupRepository, + private DeleteBackupService $deleteBackupService, + private BackupManager $backupManager ) { - $this->repository = $repository; - $this->connection = $connection; - $this->daemonBackupRepository = $daemonBackupRepository; - $this->backupManager = $backupManager; - $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 { @@ -87,10 +47,8 @@ public function setIsLocked(bool $isLocked): self * Sets the files to be ignored by this backup. * * @param string[]|null $ignored - * - * @return $this */ - public function setIgnoredFiles(?array $ignored) + public function setIgnoredFiles(?array $ignored): self { if (is_array($ignored)) { foreach ($ignored as $value) { diff --git a/app/Services/Databases/DatabaseManagementService.php b/app/Services/Databases/DatabaseManagementService.php index a10b227196..b70cb8b4dc 100644 --- a/app/Services/Databases/DatabaseManagementService.php +++ b/app/Services/Databases/DatabaseManagementService.php @@ -25,49 +25,20 @@ class DatabaseManagementService */ private const MATCH_NAME_REGEX = '/^(s[\d]+_)(.*)$/'; - /** - * @var \Illuminate\Database\ConnectionInterface - */ - private $connection; - - /** - * @var \Pterodactyl\Extensions\DynamicDatabaseConnection - */ - private $dynamic; - - /** - * @var \Illuminate\Contracts\Encryption\Encrypter - */ - private $encrypter; - - /** - * @var \Pterodactyl\Repositories\Eloquent\DatabaseRepository - */ - private $repository; - /** * Determines if the service should validate the user's ability to create an additional * database for this server. In almost all cases this should be true, but to keep things * flexible you can also set it to false and create more databases than the server is * allocated. - * - * @var bool */ - protected $validateDatabaseLimit = true; + protected bool $validateDatabaseLimit = true; - /** - * CreationService constructor. - */ public function __construct( - ConnectionInterface $connection, - DynamicDatabaseConnection $dynamic, - DatabaseRepository $repository, - Encrypter $encrypter + protected ConnectionInterface $connection, + protected DynamicDatabaseConnection $dynamic, + protected Encrypter $encrypter, + protected DatabaseRepository $repository ) { - $this->connection = $connection; - $this->dynamic = $dynamic; - $this->encrypter = $encrypter; - $this->repository = $repository; } /** @@ -82,10 +53,8 @@ public static function generateUniqueDatabaseName(string $name, int $serverId): } /** - * Set wether or not this class should validate that the server has enough slots + * Set whether this class should validate that the server has enough slots * left before creating the new database. - * - * @return $this */ public function setValidateDatabaseLimit(bool $validate): self { @@ -97,13 +66,11 @@ public function setValidateDatabaseLimit(bool $validate): self /** * Create a new database that is linked to a specific host. * - * @return \Pterodactyl\Models\Database - * * @throws \Throwable * @throws \Pterodactyl\Exceptions\Service\Database\TooManyDatabasesException * @throws \Pterodactyl\Exceptions\Service\Database\DatabaseClientFeatureNotEnabledException */ - public function create(Server $server, array $data) + public function create(Server $server, array $data): Database { if (!config('pterodactyl.client_features.databases.enabled')) { throw new DatabaseClientFeatureNotEnabledException(); @@ -169,11 +136,9 @@ public function create(Server $server, array $data) /** * Delete a database from the given host server. * - * @return bool|null - * * @throws \Exception */ - public function delete(Database $database) + public function delete(Database $database): ?bool { $this->dynamic->set('dynamic', $database->database_host_id); diff --git a/app/Services/Databases/DatabasePasswordService.php b/app/Services/Databases/DatabasePasswordService.php index aabe983881..3862b2393b 100644 --- a/app/Services/Databases/DatabasePasswordService.php +++ b/app/Services/Databases/DatabasePasswordService.php @@ -11,49 +11,23 @@ class DatabasePasswordService { - /** - * @var \Illuminate\Database\ConnectionInterface - */ - private $connection; - - /** - * @var \Pterodactyl\Extensions\DynamicDatabaseConnection - */ - private $dynamic; - - /** - * @var \Illuminate\Contracts\Encryption\Encrypter - */ - private $encrypter; - - /** - * @var \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface - */ - private $repository; - /** * DatabasePasswordService constructor. */ public function __construct( - ConnectionInterface $connection, - DatabaseRepositoryInterface $repository, - DynamicDatabaseConnection $dynamic, - Encrypter $encrypter + private ConnectionInterface $connection, + private DynamicDatabaseConnection $dynamic, + private Encrypter $encrypter, + private DatabaseRepositoryInterface $repository ) { - $this->connection = $connection; - $this->dynamic = $dynamic; - $this->encrypter = $encrypter; - $this->repository = $repository; } /** * Updates a password for a given database. * - * @param \Pterodactyl\Models\Database|int $database - * * @throws \Throwable */ - public function handle(Database $database): string + public function handle(Database|int $database): string { $password = Utilities::randomStringWithSpecialCharacters(24); diff --git a/app/Services/Databases/DeployServerDatabaseService.php b/app/Services/Databases/DeployServerDatabaseService.php index 3946cb83f8..e22eba51d4 100644 --- a/app/Services/Databases/DeployServerDatabaseService.php +++ b/app/Services/Databases/DeployServerDatabaseService.php @@ -11,18 +11,10 @@ class DeployServerDatabaseService { /** - * @var \Pterodactyl\Services\Databases\DatabaseManagementService + * DeployServerDatabaseService constructor. */ - private $managementService; - - /** - * ServerDatabaseCreationService constructor. - * - * @param \Pterodactyl\Services\Databases\DatabaseManagementService $managementService - */ - public function __construct(DatabaseManagementService $managementService) + public function __construct(private DatabaseManagementService $managementService) { - $this->managementService = $managementService; } /** diff --git a/app/Services/Databases/Hosts/HostCreationService.php b/app/Services/Databases/Hosts/HostCreationService.php index 275f4d294a..d33a15856a 100644 --- a/app/Services/Databases/Hosts/HostCreationService.php +++ b/app/Services/Databases/Hosts/HostCreationService.php @@ -11,46 +11,16 @@ class HostCreationService { - /** - * @var \Illuminate\Database\ConnectionInterface - */ - private $connection; - - /** - * @var \Illuminate\Database\DatabaseManager - */ - private $databaseManager; - - /** - * @var \Pterodactyl\Extensions\DynamicDatabaseConnection - */ - private $dynamic; - - /** - * @var \Illuminate\Contracts\Encryption\Encrypter - */ - private $encrypter; - - /** - * @var \Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface - */ - private $repository; - /** * HostCreationService constructor. */ public function __construct( - ConnectionInterface $connection, - DatabaseManager $databaseManager, - DatabaseHostRepositoryInterface $repository, - DynamicDatabaseConnection $dynamic, - Encrypter $encrypter + private ConnectionInterface $connection, + private DatabaseManager $databaseManager, + private DynamicDatabaseConnection $dynamic, + private Encrypter $encrypter, + private DatabaseHostRepositoryInterface $repository ) { - $this->connection = $connection; - $this->databaseManager = $databaseManager; - $this->dynamic = $dynamic; - $this->encrypter = $encrypter; - $this->repository = $repository; } /** diff --git a/app/Services/Databases/Hosts/HostDeletionService.php b/app/Services/Databases/Hosts/HostDeletionService.php index 1538d4823f..4a06af8da7 100644 --- a/app/Services/Databases/Hosts/HostDeletionService.php +++ b/app/Services/Databases/Hosts/HostDeletionService.php @@ -8,25 +8,13 @@ class HostDeletionService { - /** - * @var \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface - */ - private $databaseRepository; - - /** - * @var \Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface - */ - private $repository; - /** * HostDeletionService constructor. */ public function __construct( - DatabaseRepositoryInterface $databaseRepository, - DatabaseHostRepositoryInterface $repository + private DatabaseRepositoryInterface $databaseRepository, + private DatabaseHostRepositoryInterface $repository ) { - $this->databaseRepository = $databaseRepository; - $this->repository = $repository; } /** diff --git a/app/Services/Databases/Hosts/HostUpdateService.php b/app/Services/Databases/Hosts/HostUpdateService.php index 4a281b851c..363deb1b33 100644 --- a/app/Services/Databases/Hosts/HostUpdateService.php +++ b/app/Services/Databases/Hosts/HostUpdateService.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Services\Databases\Hosts; @@ -19,45 +12,15 @@ class HostUpdateService { /** - * @var \Illuminate\Database\ConnectionInterface - */ - private $connection; - - /** - * @var \Illuminate\Database\DatabaseManager - */ - private $databaseManager; - - /** - * @var \Pterodactyl\Extensions\DynamicDatabaseConnection - */ - private $dynamic; - - /** - * @var \Illuminate\Contracts\Encryption\Encrypter - */ - private $encrypter; - - /** - * @var \Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface - */ - private $repository; - - /** - * DatabaseHostService constructor. + * HostUpdateService constructor. */ public function __construct( - ConnectionInterface $connection, - DatabaseManager $databaseManager, - DatabaseHostRepositoryInterface $repository, - DynamicDatabaseConnection $dynamic, - Encrypter $encrypter + private ConnectionInterface $connection, + private DatabaseManager $databaseManager, + private DynamicDatabaseConnection $dynamic, + private Encrypter $encrypter, + private DatabaseHostRepositoryInterface $repository ) { - $this->connection = $connection; - $this->databaseManager = $databaseManager; - $this->dynamic = $dynamic; - $this->encrypter = $encrypter; - $this->repository = $repository; } /** diff --git a/app/Services/Deployment/AllocationSelectionService.php b/app/Services/Deployment/AllocationSelectionService.php index d714700b59..ae334edd25 100644 --- a/app/Services/Deployment/AllocationSelectionService.php +++ b/app/Services/Deployment/AllocationSelectionService.php @@ -10,42 +10,25 @@ class AllocationSelectionService { - /** - * @var \Pterodactyl\Contracts\Repository\AllocationRepositoryInterface - */ - private $repository; - - /** - * @var bool - */ - protected $dedicated = false; + protected bool $dedicated = false; - /** - * @var array - */ - protected $nodes = []; + protected array $nodes = []; - /** - * @var array - */ - protected $ports = []; + protected array $ports = []; /** * AllocationSelectionService constructor. */ - public function __construct(AllocationRepositoryInterface $repository) + public function __construct(private AllocationRepositoryInterface $repository) { - $this->repository = $repository; } /** * Toggle if the selected allocation should be the only allocation belonging * to the given IP address. If true an allocation will not be selected if an IP * already has another server set to use on if its allocations. - * - * @return $this */ - public function setDedicated(bool $dedicated) + public function setDedicated(bool $dedicated): self { $this->dedicated = $dedicated; @@ -55,10 +38,8 @@ public function setDedicated(bool $dedicated) /** * A list of node IDs that should be used when selecting an allocation. If empty, all * nodes will be used to filter with. - * - * @return $this */ - public function setNodes(array $nodes) + public function setNodes(array $nodes): self { $this->nodes = $nodes; @@ -70,11 +51,9 @@ public function setNodes(array $nodes) * empty, all ports will be considered when finding an allocation. If set, only ports appearing * in the array or range will be used. * - * @return $this - * * @throws \Pterodactyl\Exceptions\DisplayException */ - public function setPorts(array $ports) + public function setPorts(array $ports): self { $stored = []; foreach ($ports as $port) { diff --git a/app/Services/Deployment/FindViableNodesService.php b/app/Services/Deployment/FindViableNodesService.php index 5c5a5138e6..71c830bf9e 100644 --- a/app/Services/Deployment/FindViableNodesService.php +++ b/app/Services/Deployment/FindViableNodesService.php @@ -4,29 +4,18 @@ use Pterodactyl\Models\Node; use Webmozart\Assert\Assert; +use Illuminate\Support\Collection; +use Illuminate\Contracts\Pagination\LengthAwarePaginator; use Pterodactyl\Exceptions\Service\Deployment\NoViableNodeException; class FindViableNodesService { - /** - * @var array - */ - protected $locations = []; - - /** - * @var int - */ - protected $disk; - - /** - * @var int - */ - protected $memory; + protected array $locations = []; + protected ?int $disk = null; + protected ?int $memory = null; /** * Set the locations that should be searched through to locate available nodes. - * - * @return $this */ public function setLocations(array $locations): self { @@ -41,8 +30,6 @@ public function setLocations(array $locations): self * Set the amount of disk that will be used by the server being created. Nodes will be * filtered out if they do not have enough available free disk space for this server * to be placed on. - * - * @return $this */ public function setDisk(int $disk): self { @@ -54,8 +41,6 @@ public function setDisk(int $disk): self /** * Set the amount of memory that this server will be using. As with disk space, nodes that * do not have enough free memory will be filtered out. - * - * @return $this */ public function setMemory(int $memory): self { @@ -79,11 +64,9 @@ public function setMemory(int $memory): self * If "null" is provided as the value no pagination will * be used. * - * @return \Illuminate\Support\Collection|\Illuminate\Contracts\Pagination\LengthAwarePaginator - * * @throws \Pterodactyl\Exceptions\Service\Deployment\NoViableNodeException */ - public function handle(int $perPage = null, int $page = null) + public function handle(int $perPage = null, int $page = null): LengthAwarePaginator|Collection { Assert::integer($this->disk, 'Disk space must be an int, got %s'); Assert::integer($this->memory, 'Memory usage must be an int, got %s'); diff --git a/app/Services/Eggs/EggConfigurationService.php b/app/Services/Eggs/EggConfigurationService.php index bfcdc1eee0..4a61c2ea6c 100644 --- a/app/Services/Eggs/EggConfigurationService.php +++ b/app/Services/Eggs/EggConfigurationService.php @@ -9,17 +9,11 @@ class EggConfigurationService { - /** - * @var \Pterodactyl\Services\Servers\ServerConfigurationStructureService - */ - private $configurationStructureService; - /** * EggConfigurationService constructor. */ - public function __construct(ServerConfigurationStructureService $configurationStructureService) + public function __construct(private ServerConfigurationStructureService $configurationStructureService) { - $this->configurationStructureService = $configurationStructureService; } /** @@ -41,10 +35,8 @@ public function handle(Server $server): array /** * Convert the "done" variable into an array if it is not currently one. - * - * @return array */ - protected function convertStartupToNewFormat(array $startup) + protected function convertStartupToNewFormat(array $startup): array { $done = Arr::get($startup, 'done'); @@ -85,10 +77,7 @@ protected function convertStopToNewFormat(string $stop): array ]; } - /** - * @return array - */ - protected function replacePlaceholders(Server $server, object $configs) + protected function replacePlaceholders(Server $server, object $configs): array { // Get the legacy configuration structure for the server so that we // can property map the egg placeholders to values. @@ -161,20 +150,15 @@ protected function replaceLegacyModifiers(string $key, string $value): string case 'env.SERVER_PORT': $replace = 'server.build.default.port'; break; - // By default we don't need to change anything, only if we ended up matching a specific legacy item. default: + // By default, we don't need to change anything, only if we ended up matching a specific legacy item. $replace = $key; } return str_replace("{{{$key}}}", "{{{$replace}}}", $value); } - /** - * @param mixed $value - * - * @return mixed|null - */ - protected function matchAndReplaceKeys($value, array $structure) + protected function matchAndReplaceKeys(mixed $value, array $structure): mixed { preg_match_all('/{{(?[\w.-]*)}}/', $value, $matches); @@ -229,12 +213,8 @@ protected function matchAndReplaceKeys($value, array $structure) * Iterates over a set of "find" values for a given file in the parser configuration. If * the value of the line match is something iterable, continue iterating, otherwise perform * a match & replace. - * - * @param mixed $data - * - * @return mixed */ - private function iterate($data, array $structure) + private function iterate(mixed $data, array $structure): mixed { if (!is_iterable($data) && !is_object($data)) { return $data; diff --git a/app/Services/Eggs/EggCreationService.php b/app/Services/Eggs/EggCreationService.php index d7b5bb42bc..b084b0ccad 100644 --- a/app/Services/Eggs/EggCreationService.php +++ b/app/Services/Eggs/EggCreationService.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Services\Eggs; @@ -18,23 +11,11 @@ // When a mommy and a daddy pterodactyl really like each other... class EggCreationService { - /** - * @var \Illuminate\Contracts\Config\Repository - */ - protected $config; - - /** - * @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface - */ - protected $repository; - /** * EggCreationService constructor. */ - public function __construct(ConfigRepository $config, EggRepositoryInterface $repository) + public function __construct(private ConfigRepository $config, private EggRepositoryInterface $repository) { - $this->config = $config; - $this->repository = $repository; } /** diff --git a/app/Services/Eggs/EggDeletionService.php b/app/Services/Eggs/EggDeletionService.php index 1c8f8cf665..7e40133513 100644 --- a/app/Services/Eggs/EggDeletionService.php +++ b/app/Services/Eggs/EggDeletionService.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Services\Eggs; @@ -16,25 +9,13 @@ class EggDeletionService { - /** - * @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface - */ - protected $repository; - - /** - * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface - */ - protected $serverRepository; - /** * EggDeletionService constructor. */ public function __construct( - ServerRepositoryInterface $serverRepository, - EggRepositoryInterface $repository + protected ServerRepositoryInterface $serverRepository, + protected EggRepositoryInterface $repository ) { - $this->repository = $repository; - $this->serverRepository = $serverRepository; } /** diff --git a/app/Services/Eggs/EggParserService.php b/app/Services/Eggs/EggParserService.php index ff84fdfd88..6d8545bc2c 100644 --- a/app/Services/Eggs/EggParserService.php +++ b/app/Services/Eggs/EggParserService.php @@ -13,6 +13,7 @@ class EggParserService /** * Takes an uploaded file and parses out the egg configuration from within. * + * @throws \JsonException * @throws \Pterodactyl\Exceptions\Service\InvalidFileUploadException */ public function handle(UploadedFile $file): array diff --git a/app/Services/Eggs/EggUpdateService.php b/app/Services/Eggs/EggUpdateService.php index e0cabbb05e..7ca442ba33 100644 --- a/app/Services/Eggs/EggUpdateService.php +++ b/app/Services/Eggs/EggUpdateService.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Services\Eggs; @@ -15,17 +8,11 @@ class EggUpdateService { - /** - * @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface - */ - protected $repository; - /** * EggUpdateService constructor. */ - public function __construct(EggRepositoryInterface $repository) + public function __construct(protected EggRepositoryInterface $repository) { - $this->repository = $repository; } /** @@ -35,7 +22,7 @@ public function __construct(EggRepositoryInterface $repository) * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException * @throws \Pterodactyl\Exceptions\Service\Egg\NoParentConfigurationFoundException */ - public function handle(Egg $egg, array $data) + public function handle(Egg $egg, array $data): void { if (!is_null(array_get($data, 'config_from'))) { $results = $this->repository->findCountWhere([ diff --git a/app/Services/Eggs/Scripts/InstallScriptService.php b/app/Services/Eggs/Scripts/InstallScriptService.php index ecd1dc1f3a..3341572363 100644 --- a/app/Services/Eggs/Scripts/InstallScriptService.php +++ b/app/Services/Eggs/Scripts/InstallScriptService.php @@ -8,29 +8,21 @@ class InstallScriptService { - /** - * @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface - */ - protected $repository; - /** * InstallScriptService constructor. */ - public function __construct(EggRepositoryInterface $repository) + public function __construct(protected EggRepositoryInterface $repository) { - $this->repository = $repository; } /** * Modify the install script for a given Egg. * - * @param int|\Pterodactyl\Models\Egg $egg - * * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException * @throws \Pterodactyl\Exceptions\Service\Egg\InvalidCopyFromException */ - public function handle(Egg $egg, array $data) + public function handle(Egg $egg, array $data): void { if (!is_null(array_get($data, 'copy_script_from'))) { if (!$this->repository->isCopyableScript(array_get($data, 'copy_script_from'), $egg->nest_id)) { diff --git a/app/Services/Eggs/Sharing/EggExporterService.php b/app/Services/Eggs/Sharing/EggExporterService.php index 26723747a0..706297b3de 100644 --- a/app/Services/Eggs/Sharing/EggExporterService.php +++ b/app/Services/Eggs/Sharing/EggExporterService.php @@ -10,17 +10,11 @@ class EggExporterService { - /** - * @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface - */ - protected $repository; - /** * EggExporterService constructor. */ - public function __construct(EggRepositoryInterface $repository) + public function __construct(protected EggRepositoryInterface $repository) { - $this->repository = $repository; } /** @@ -38,7 +32,7 @@ public function handle(int $egg): string 'version' => Egg::EXPORT_VERSION, 'update_url' => $egg->update_url, ], - 'exported_at' => Carbon::now()->toIso8601String(), + 'exported_at' => Carbon::now()->toAtomString(), 'name' => $egg->name, 'author' => $egg->author, 'description' => $egg->description, diff --git a/app/Services/Eggs/Sharing/EggImporterService.php b/app/Services/Eggs/Sharing/EggImporterService.php index 746ce26be7..ecd6eadb6a 100644 --- a/app/Services/Eggs/Sharing/EggImporterService.php +++ b/app/Services/Eggs/Sharing/EggImporterService.php @@ -13,14 +13,8 @@ class EggImporterService { - protected ConnectionInterface $connection; - - protected EggParserService $parser; - - public function __construct(ConnectionInterface $connection, EggParserService $parser) + public function __construct(protected ConnectionInterface $connection, protected EggParserService $parser) { - $this->connection = $connection; - $this->parser = $parser; } /** diff --git a/app/Services/Eggs/Sharing/EggUpdateImporterService.php b/app/Services/Eggs/Sharing/EggUpdateImporterService.php index 7d28116420..89a1f92873 100644 --- a/app/Services/Eggs/Sharing/EggUpdateImporterService.php +++ b/app/Services/Eggs/Sharing/EggUpdateImporterService.php @@ -11,17 +11,11 @@ class EggUpdateImporterService { - protected ConnectionInterface $connection; - - protected EggParserService $parser; - /** * EggUpdateImporterService constructor. */ - public function __construct(ConnectionInterface $connection, EggParserService $parser) + public function __construct(protected ConnectionInterface $connection, protected EggParserService $parser) { - $this->connection = $connection; - $this->parser = $parser; } /** diff --git a/app/Services/Eggs/Variables/VariableCreationService.php b/app/Services/Eggs/Variables/VariableCreationService.php index fa758265b0..a7e19f6bac 100644 --- a/app/Services/Eggs/Variables/VariableCreationService.php +++ b/app/Services/Eggs/Variables/VariableCreationService.php @@ -3,8 +3,8 @@ namespace Pterodactyl\Services\Eggs\Variables; use Pterodactyl\Models\EggVariable; -use Illuminate\Contracts\Validation\Factory; use Pterodactyl\Traits\Services\ValidatesValidationRules; +use Illuminate\Contracts\Validation\Factory as ValidationFactory; use Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface; use Pterodactyl\Exceptions\Service\Egg\Variable\ReservedVariableNameException; @@ -12,30 +12,18 @@ class VariableCreationService { use ValidatesValidationRules; - /** - * @var \Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface - */ - private $repository; - - /** - * @var \Illuminate\Contracts\Validation\Factory - */ - private $validator; - /** * VariableCreationService constructor. */ - public function __construct(EggVariableRepositoryInterface $repository, Factory $validator) + public function __construct(private EggVariableRepositoryInterface $repository, private ValidationFactory $validator) { - $this->repository = $repository; - $this->validator = $validator; } /** * Return the validation factory instance to be used by rule validation * checking in the trait. */ - protected function getValidator(): Factory + protected function getValidator(): ValidationFactory { return $this->validator; } diff --git a/app/Services/Eggs/Variables/VariableUpdateService.php b/app/Services/Eggs/Variables/VariableUpdateService.php index da4426c339..ed70b260fe 100644 --- a/app/Services/Eggs/Variables/VariableUpdateService.php +++ b/app/Services/Eggs/Variables/VariableUpdateService.php @@ -4,9 +4,9 @@ use Illuminate\Support\Str; use Pterodactyl\Models\EggVariable; -use Illuminate\Contracts\Validation\Factory; use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Traits\Services\ValidatesValidationRules; +use Illuminate\Contracts\Validation\Factory as ValidationFactory; use Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface; use Pterodactyl\Exceptions\Service\Egg\Variable\ReservedVariableNameException; @@ -14,30 +14,18 @@ class VariableUpdateService { use ValidatesValidationRules; - /** - * @var \Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface - */ - private $repository; - - /** - * @var \Illuminate\Contracts\Validation\Factory - */ - private $validator; - /** * VariableUpdateService constructor. */ - public function __construct(EggVariableRepositoryInterface $repository, Factory $validator) + public function __construct(private EggVariableRepositoryInterface $repository, private ValidationFactory $validator) { - $this->repository = $repository; - $this->validator = $validator; } /** * Return the validation factory instance to be used by rule validation * checking in the trait. */ - protected function getValidator(): Factory + protected function getValidator(): ValidationFactory { return $this->validator; } @@ -45,14 +33,12 @@ protected function getValidator(): Factory /** * Update a specific egg variable. * - * @return mixed - * * @throws \Pterodactyl\Exceptions\DisplayException * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException * @throws \Pterodactyl\Exceptions\Service\Egg\Variable\ReservedVariableNameException */ - public function handle(EggVariable $variable, array $data) + public function handle(EggVariable $variable, array $data): mixed { if (!is_null(array_get($data, 'env_variable'))) { if (in_array(strtoupper(array_get($data, 'env_variable')), explode(',', EggVariable::RESERVED_ENV_NAMES))) { diff --git a/app/Services/Helpers/ApiAllowedIpsValidatorService.php b/app/Services/Helpers/ApiAllowedIpsValidatorService.php deleted file mode 100644 index 7051cd5399..0000000000 --- a/app/Services/Helpers/ApiAllowedIpsValidatorService.php +++ /dev/null @@ -1,8 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ diff --git a/app/Services/Helpers/AssetHashService.php b/app/Services/Helpers/AssetHashService.php index 4b1bf7e0b8..ef29c03a90 100644 --- a/app/Services/Helpers/AssetHashService.php +++ b/app/Services/Helpers/AssetHashService.php @@ -4,7 +4,7 @@ use Illuminate\Support\Arr; use Illuminate\Filesystem\FilesystemManager; -use Illuminate\Contracts\Foundation\Application; +use Illuminate\Contracts\Filesystem\Filesystem; class AssetHashService { @@ -13,34 +13,20 @@ class AssetHashService */ public const MANIFEST_PATH = './assets/manifest.json'; - /** - * @var \Illuminate\Contracts\Filesystem\Filesystem - */ - private $filesystem; + private Filesystem $filesystem; - /** - * @var \Illuminate\Contracts\Foundation\Application - */ - private $application; - - /** - * @var array|null - */ - protected static $manifest; + protected static mixed $manifest; /** * AssetHashService constructor. */ - public function __construct(Application $application, FilesystemManager $filesystem) + public function __construct(FilesystemManager $filesystem) { - $this->application = $application; $this->filesystem = $filesystem->createLocalDriver(['root' => public_path()]); } /** * Modify a URL to append the asset hash. - * - * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException */ public function url(string $resource): string { @@ -52,8 +38,6 @@ public function url(string $resource): string /** * Return the data integrity hash for a resource. - * - * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException */ public function integrity(string $resource): string { @@ -65,8 +49,6 @@ public function integrity(string $resource): string /** * Return a built CSS import using the provided URL. - * - * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException */ public function css(string $resource): string { @@ -92,8 +74,6 @@ public function css(string $resource): string /** * Return a built JS import using the provided URL. - * - * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException */ public function js(string $resource): string { @@ -116,8 +96,6 @@ public function js(string $resource): string /** * Get the asset manifest and store it in the cache for quicker lookups. - * - * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException */ protected function manifest(): array { diff --git a/app/Services/Helpers/SoftwareVersionService.php b/app/Services/Helpers/SoftwareVersionService.php index 14a2b437d5..2122b397b1 100644 --- a/app/Services/Helpers/SoftwareVersionService.php +++ b/app/Services/Helpers/SoftwareVersionService.php @@ -13,98 +13,68 @@ class SoftwareVersionService { public const VERSION_CACHE_KEY = 'pterodactyl:versioning_data'; - /** - * @var array - */ - private static $result; - - /** - * @var \Illuminate\Contracts\Cache\Repository - */ - protected $cache; - - /** - * @var \GuzzleHttp\Client - */ - protected $client; + private static array $result; /** * SoftwareVersionService constructor. */ public function __construct( - CacheRepository $cache, - Client $client + protected CacheRepository $cache, + protected Client $client ) { - $this->cache = $cache; - $this->client = $client; - self::$result = $this->cacheVersionData(); } /** * Get the latest version of the panel from the CDN servers. - * - * @return string */ - public function getPanel() + public function getPanel(): string { return Arr::get(self::$result, 'panel') ?? 'error'; } /** * Get the latest version of the daemon from the CDN servers. - * - * @return string */ - public function getDaemon() + public function getDaemon(): string { return Arr::get(self::$result, 'wings') ?? 'error'; } /** * Get the URL to the discord server. - * - * @return string */ - public function getDiscord() + public function getDiscord(): string { return Arr::get(self::$result, 'discord') ?? 'https://pterodactyl.io/discord'; } /** * Get the URL for donations. - * - * @return string */ - public function getDonations() + public function getDonations(): string { - return Arr::get(self::$result, 'donations') ?? 'https://paypal.me/PterodactylSoftware'; + return Arr::get(self::$result, 'donations') ?? 'https://github.com/sponsors/matthewpi'; } /** * Determine if the current version of the panel is the latest. - * - * @return bool */ - public function isLatestPanel() + public function isLatestPanel(): bool { - if (config()->get('app.version') === 'canary') { + if (config('app.version') === 'canary') { return true; } - return version_compare(config()->get('app.version'), $this->getPanel()) >= 0; + return version_compare(config('app.version'), $this->getPanel()) >= 0; } /** * Determine if a passed daemon version string is the latest. - * - * @param string $version - * - * @return bool */ - public function isLatestDaemon($version) + public function isLatestDaemon(string $version): bool { - if ($version === '0.0.0-canary') { + if ($version === 'develop') { return true; } @@ -113,21 +83,19 @@ public function isLatestDaemon($version) /** * Keeps the versioning cache up-to-date with the latest results from the CDN. - * - * @return array */ - protected function cacheVersionData() + protected function cacheVersionData(): array { - return $this->cache->remember(self::VERSION_CACHE_KEY, CarbonImmutable::now()->addMinutes(config()->get('pterodactyl.cdn.cache_time', 60)), function () { + return $this->cache->remember(self::VERSION_CACHE_KEY, CarbonImmutable::now()->addMinutes(config('pterodactyl.cdn.cache_time', 60)), function () { try { - $response = $this->client->request('GET', config()->get('pterodactyl.cdn.url')); + $response = $this->client->request('GET', config('pterodactyl.cdn.url')); if ($response->getStatusCode() === 200) { return json_decode($response->getBody(), true); } throw new CdnVersionFetchingException(); - } catch (Exception $exception) { + } catch (Exception) { return []; } }); diff --git a/app/Services/Locations/LocationCreationService.php b/app/Services/Locations/LocationCreationService.php index cff493a8bf..b1a3ec9956 100644 --- a/app/Services/Locations/LocationCreationService.php +++ b/app/Services/Locations/LocationCreationService.php @@ -1,39 +1,25 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Services\Locations; +use Pterodactyl\Models\Location; use Pterodactyl\Contracts\Repository\LocationRepositoryInterface; class LocationCreationService { - /** - * @var \Pterodactyl\Contracts\Repository\LocationRepositoryInterface - */ - protected $repository; - /** * LocationCreationService constructor. */ - public function __construct(LocationRepositoryInterface $repository) + public function __construct(protected LocationRepositoryInterface $repository) { - $this->repository = $repository; } /** * Create a new location. * - * @return \Pterodactyl\Models\Location - * * @throws \Pterodactyl\Exceptions\Model\DataValidationException */ - public function handle(array $data) + public function handle(array $data): Location { return $this->repository->create($data); } diff --git a/app/Services/Locations/LocationDeletionService.php b/app/Services/Locations/LocationDeletionService.php index 2ffd38187c..5b4b9eba4e 100644 --- a/app/Services/Locations/LocationDeletionService.php +++ b/app/Services/Locations/LocationDeletionService.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Services\Locations; @@ -17,37 +10,21 @@ class LocationDeletionService { - /** - * @var \Pterodactyl\Contracts\Repository\NodeRepositoryInterface - */ - protected $nodeRepository; - - /** - * @var \Pterodactyl\Contracts\Repository\LocationRepositoryInterface - */ - protected $repository; - /** * LocationDeletionService constructor. */ public function __construct( - LocationRepositoryInterface $repository, - NodeRepositoryInterface $nodeRepository + protected LocationRepositoryInterface $repository, + protected NodeRepositoryInterface $nodeRepository ) { - $this->nodeRepository = $nodeRepository; - $this->repository = $repository; } /** * Delete an existing location. * - * @param int|\Pterodactyl\Models\Location $location - * - * @return int|null - * * @throws \Pterodactyl\Exceptions\Service\Location\HasActiveNodesException */ - public function handle($location) + public function handle(Location|int $location): ?int { $location = ($location instanceof Location) ? $location->id : $location; diff --git a/app/Services/Locations/LocationUpdateService.php b/app/Services/Locations/LocationUpdateService.php index 87399201f6..cf24459e92 100644 --- a/app/Services/Locations/LocationUpdateService.php +++ b/app/Services/Locations/LocationUpdateService.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Services\Locations; @@ -14,30 +7,20 @@ class LocationUpdateService { - /** - * @var \Pterodactyl\Contracts\Repository\LocationRepositoryInterface - */ - protected $repository; - /** * LocationUpdateService constructor. */ - public function __construct(LocationRepositoryInterface $repository) + public function __construct(protected LocationRepositoryInterface $repository) { - $this->repository = $repository; } /** * Update an existing location. * - * @param int|\Pterodactyl\Models\Location $location - * - * @return \Pterodactyl\Models\Location - * * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function handle($location, array $data) + public function handle(Location|int $location, array $data): Location { $location = ($location instanceof Location) ? $location->id : $location; diff --git a/app/Services/Nests/NestCreationService.php b/app/Services/Nests/NestCreationService.php index af56496d19..c3513aefe8 100644 --- a/app/Services/Nests/NestCreationService.php +++ b/app/Services/Nests/NestCreationService.php @@ -9,23 +9,11 @@ class NestCreationService { - /** - * @var \Illuminate\Contracts\Config\Repository - */ - private $config; - - /** - * @var \Pterodactyl\Contracts\Repository\NestRepositoryInterface - */ - private $repository; - /** * NestCreationService constructor. */ - public function __construct(ConfigRepository $config, NestRepositoryInterface $repository) + public function __construct(private ConfigRepository $config, private NestRepositoryInterface $repository) { - $this->config = $config; - $this->repository = $repository; } /** diff --git a/app/Services/Nests/NestDeletionService.php b/app/Services/Nests/NestDeletionService.php index 777a41b683..6babf45c3e 100644 --- a/app/Services/Nests/NestDeletionService.php +++ b/app/Services/Nests/NestDeletionService.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Services\Nests; @@ -15,25 +8,13 @@ class NestDeletionService { - /** - * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface - */ - protected $serverRepository; - - /** - * @var \Pterodactyl\Contracts\Repository\NestRepositoryInterface - */ - protected $repository; - /** * NestDeletionService constructor. */ public function __construct( - ServerRepositoryInterface $serverRepository, - NestRepositoryInterface $repository + protected ServerRepositoryInterface $serverRepository, + protected NestRepositoryInterface $repository ) { - $this->serverRepository = $serverRepository; - $this->repository = $repository; } /** diff --git a/app/Services/Nests/NestUpdateService.php b/app/Services/Nests/NestUpdateService.php index 7772ff380d..772421e42e 100644 --- a/app/Services/Nests/NestUpdateService.php +++ b/app/Services/Nests/NestUpdateService.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Services\Nests; @@ -13,17 +6,11 @@ class NestUpdateService { - /** - * @var \Pterodactyl\Contracts\Repository\NestRepositoryInterface - */ - protected $repository; - /** * NestUpdateService constructor. */ - public function __construct(NestRepositoryInterface $repository) + public function __construct(protected NestRepositoryInterface $repository) { - $this->repository = $repository; } /** @@ -32,7 +19,7 @@ public function __construct(NestRepositoryInterface $repository) * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function handle(int $nest, array $data) + public function handle(int $nest, array $data): void { if (!is_null(array_get($data, 'author'))) { unset($data['author']); diff --git a/app/Services/Nodes/NodeCreationService.php b/app/Services/Nodes/NodeCreationService.php index e8ea5b0d8b..e4946bc209 100644 --- a/app/Services/Nodes/NodeCreationService.php +++ b/app/Services/Nodes/NodeCreationService.php @@ -11,32 +11,18 @@ class NodeCreationService { /** - * @var \Pterodactyl\Contracts\Repository\NodeRepositoryInterface + * NodeCreationService constructor. */ - protected $repository; - - /** - * @var \Illuminate\Contracts\Encryption\Encrypter - */ - private $encrypter; - - /** - * CreationService constructor. - */ - public function __construct(Encrypter $encrypter, NodeRepositoryInterface $repository) + public function __construct(private Encrypter $encrypter, protected NodeRepositoryInterface $repository) { - $this->repository = $repository; - $this->encrypter = $encrypter; } /** * Create a new node on the panel. * - * @return \Pterodactyl\Models\Node - * * @throws \Pterodactyl\Exceptions\Model\DataValidationException */ - public function handle(array $data) + public function handle(array $data): Node { $data['uuid'] = Uuid::uuid4()->toString(); $data['daemon_token'] = $this->encrypter->encrypt(Str::random(Node::DAEMON_TOKEN_LENGTH)); diff --git a/app/Services/Nodes/NodeDeletionService.php b/app/Services/Nodes/NodeDeletionService.php index e77104e09a..adb9a0628a 100644 --- a/app/Services/Nodes/NodeDeletionService.php +++ b/app/Services/Nodes/NodeDeletionService.php @@ -1,13 +1,5 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - namespace Pterodactyl\Services\Nodes; use Pterodactyl\Models\Node; @@ -19,43 +11,21 @@ class NodeDeletionService { /** - * @var \Pterodactyl\Contracts\Repository\NodeRepositoryInterface - */ - protected $repository; - - /** - * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface - */ - protected $serverRepository; - - /** - * @var \Illuminate\Contracts\Translation\Translator - */ - protected $translator; - - /** - * DeletionService constructor. + * NodeDeletionService constructor. */ public function __construct( - NodeRepositoryInterface $repository, - ServerRepositoryInterface $serverRepository, - Translator $translator + protected NodeRepositoryInterface $repository, + protected ServerRepositoryInterface $serverRepository, + protected Translator $translator ) { - $this->repository = $repository; - $this->serverRepository = $serverRepository; - $this->translator = $translator; } /** * Delete a node from the panel if no servers are attached to it. * - * @param int|\Pterodactyl\Models\Node $node - * - * @return bool|null - * * @throws \Pterodactyl\Exceptions\Service\HasActiveServersException */ - public function handle($node) + public function handle(int|Node $node): int { if ($node instanceof Node) { $node = $node->id; diff --git a/app/Services/Nodes/NodeJWTService.php b/app/Services/Nodes/NodeJWTService.php index d9473d90ad..fc34494e22 100644 --- a/app/Services/Nodes/NodeJWTService.php +++ b/app/Services/Nodes/NodeJWTService.php @@ -7,6 +7,7 @@ use Illuminate\Support\Str; use Pterodactyl\Models\Node; use Pterodactyl\Models\User; +use Lcobucci\JWT\Token\Plain; use Lcobucci\JWT\Configuration; use Lcobucci\JWT\Signer\Hmac\Sha256; use Lcobucci\JWT\Signer\Key\InMemory; @@ -18,19 +19,14 @@ class NodeJWTService private ?User $user = null; - /** - * @var \DateTimeImmutable|null - */ - private $expiresAt; + private ?DateTimeImmutable $expiresAt; private ?string $subject = null; /** * Set the claims to include in this JWT. - * - * @return $this */ - public function setClaims(array $claims) + public function setClaims(array $claims): self { $this->claims = $claims; @@ -48,20 +44,14 @@ public function setUser(User $user): self return $this; } - /** - * @return $this - */ - public function setExpiresAt(DateTimeImmutable $date) + public function setExpiresAt(DateTimeImmutable $date): self { $this->expiresAt = $date; return $this; } - /** - * @return $this - */ - public function setSubject(string $subject) + public function setSubject(string $subject): self { $this->subject = $subject; @@ -70,12 +60,8 @@ public function setSubject(string $subject) /** * Generate a new JWT for a given node. - * - * @param string|null $identifiedBy - * - * @return \Lcobucci\JWT\Token\Plain */ - public function handle(Node $node, string $identifiedBy, string $algo = 'md5') + public function handle(Node $node, ?string $identifiedBy, string $algo = 'md5'): Plain { $identifier = hash($algo, $identifiedBy); $config = Configuration::forSymmetricSigner(new Sha256(), InMemory::plainText($node->getDecryptedKey())); @@ -112,7 +98,7 @@ public function handle(Node $node, string $identifiedBy, string $algo = 'md5') } return $builder - ->withClaim('unique_id', Str::random(16)) + ->withClaim('unique_id', Str::random()) ->getToken($config->signer(), $config->signingKey()); } } diff --git a/app/Services/Nodes/NodeUpdateService.php b/app/Services/Nodes/NodeUpdateService.php index 9ba56fb4e8..28733e35a3 100644 --- a/app/Services/Nodes/NodeUpdateService.php +++ b/app/Services/Nodes/NodeUpdateService.php @@ -15,48 +15,22 @@ class NodeUpdateService { /** - * @var \Illuminate\Database\ConnectionInterface - */ - private $connection; - - /** - * @var \Pterodactyl\Repositories\Wings\DaemonConfigurationRepository - */ - private $configurationRepository; - - /** - * @var \Illuminate\Contracts\Encryption\Encrypter - */ - private $encrypter; - - /** - * @var \Pterodactyl\Repositories\Eloquent\NodeRepository - */ - private $repository; - - /** - * UpdateService constructor. + * NodeUpdateService constructor. */ public function __construct( - ConnectionInterface $connection, - Encrypter $encrypter, - DaemonConfigurationRepository $configurationRepository, - NodeRepository $repository + private ConnectionInterface $connection, + private DaemonConfigurationRepository $configurationRepository, + private Encrypter $encrypter, + private NodeRepository $repository ) { - $this->connection = $connection; - $this->configurationRepository = $configurationRepository; - $this->encrypter = $encrypter; - $this->repository = $repository; } /** * Update the configuration values for a given node on the machine. * - * @return \Pterodactyl\Models\Node - * * @throws \Throwable */ - public function handle(Node $node, array $data, bool $resetToken = false) + public function handle(Node $node, array $data, bool $resetToken = false): Node { if ($resetToken) { $data['daemon_token'] = $this->encrypter->encrypt(Str::random(Node::DAEMON_TOKEN_LENGTH)); @@ -87,7 +61,7 @@ public function handle(Node $node, array $data, bool $resetToken = false) // but something went wrong with Wings we just want to store the update and let the user manually // make changes as needed. // - // This avoids issues with proxies such as CloudFlare which will see Wings as offline and then + // This avoids issues with proxies such as Cloudflare which will see Wings as offline and then // inject their own response pages, causing this logic to get fucked up. // // @see https://github.com/pterodactyl/panel/issues/2712 diff --git a/app/Services/Schedules/ProcessScheduleService.php b/app/Services/Schedules/ProcessScheduleService.php index a131ad5739..cfbc7e5cad 100644 --- a/app/Services/Schedules/ProcessScheduleService.php +++ b/app/Services/Schedules/ProcessScheduleService.php @@ -13,29 +13,11 @@ class ProcessScheduleService { - /** - * @var \Illuminate\Contracts\Bus\Dispatcher - */ - private $dispatcher; - - /** - * @var \Illuminate\Database\ConnectionInterface - */ - private $connection; - - /** - * @var \Pterodactyl\Repositories\Wings\DaemonServerRepository - */ - private $serverRepository; - /** * ProcessScheduleService constructor. */ - public function __construct(ConnectionInterface $connection, DaemonServerRepository $serverRepository, Dispatcher $dispatcher) + public function __construct(private ConnectionInterface $connection, private Dispatcher $dispatcher, private DaemonServerRepository $serverRepository) { - $this->dispatcher = $dispatcher; - $this->connection = $connection; - $this->serverRepository = $serverRepository; } /** @@ -43,7 +25,7 @@ public function __construct(ConnectionInterface $connection, DaemonServerReposit * * @throws \Throwable */ - public function handle(Schedule $schedule, bool $now = false) + public function handle(Schedule $schedule, bool $now = false): void { /** @var \Pterodactyl\Models\Task $task */ $task = $schedule->tasks()->orderBy('sequence_id')->first(); diff --git a/app/Services/Servers/BuildModificationService.php b/app/Services/Servers/BuildModificationService.php index 224abc12fc..05553d7f10 100644 --- a/app/Services/Servers/BuildModificationService.php +++ b/app/Services/Servers/BuildModificationService.php @@ -14,45 +14,23 @@ class BuildModificationService { - /** - * @var \Illuminate\Database\ConnectionInterface - */ - private $connection; - - /** - * @var \Pterodactyl\Repositories\Wings\DaemonServerRepository - */ - private $daemonServerRepository; - - /** - * @var \Pterodactyl\Services\Servers\ServerConfigurationStructureService - */ - private $structureService; - /** * BuildModificationService constructor. - * - * @param \Pterodactyl\Services\Servers\ServerConfigurationStructureService $structureService */ public function __construct( - ServerConfigurationStructureService $structureService, - ConnectionInterface $connection, - DaemonServerRepository $daemonServerRepository + private ConnectionInterface $connection, + private DaemonServerRepository $daemonServerRepository, + private ServerConfigurationStructureService $structureService ) { - $this->daemonServerRepository = $daemonServerRepository; - $this->connection = $connection; - $this->structureService = $structureService; } /** * Change the build details for a specified server. * - * @return \Pterodactyl\Models\Server - * * @throws \Throwable * @throws \Pterodactyl\Exceptions\DisplayException */ - public function handle(Server $server, array $data) + public function handle(Server $server, array $data): Server { /** @var \Pterodactyl\Models\Server $server */ $server = $this->connection->transaction(function () use ($server, $data) { @@ -61,7 +39,7 @@ public function handle(Server $server, array $data) if (isset($data['allocation_id']) && $data['allocation_id'] != $server->allocation_id) { try { Allocation::query()->where('id', $data['allocation_id'])->where('server_id', $server->id)->firstOrFail(); - } catch (ModelNotFoundException $ex) { + } catch (ModelNotFoundException) { throw new DisplayException('The requested default allocation is not currently assigned to this server.'); } } @@ -101,7 +79,7 @@ public function handle(Server $server, array $data) * * @throws \Pterodactyl\Exceptions\DisplayException */ - private function processAllocations(Server $server, array &$data) + private function processAllocations(Server $server, array &$data): void { if (empty($data['add_allocations']) && empty($data['remove_allocations'])) { return; diff --git a/app/Services/Servers/DetailsModificationService.php b/app/Services/Servers/DetailsModificationService.php index cbe2a6ed9e..d135c0495d 100644 --- a/app/Services/Servers/DetailsModificationService.php +++ b/app/Services/Servers/DetailsModificationService.php @@ -13,23 +13,11 @@ class DetailsModificationService { use ReturnsUpdatedModels; - /** - * @var \Illuminate\Database\ConnectionInterface - */ - private $connection; - - /** - * @var \Pterodactyl\Repositories\Wings\DaemonServerRepository - */ - private $serverRepository; - /** * DetailsModificationService constructor. */ - public function __construct(ConnectionInterface $connection, DaemonServerRepository $serverRepository) + public function __construct(private ConnectionInterface $connection, private DaemonServerRepository $serverRepository) { - $this->connection = $connection; - $this->serverRepository = $serverRepository; } /** @@ -57,7 +45,7 @@ public function handle(Server $server, array $data): Server $this->serverRepository->setServer($server)->revokeUserJTI($owner); } catch (DaemonConnectionException $exception) { // Do nothing. A failure here is not ideal, but it is likely to be caused by Wings - // being offline, or in an entirely broken state. Remeber, these tokens reset every + // being offline, or in an entirely broken state. Remember, these tokens reset every // few minutes by default, we're just trying to help it along a little quicker. } } diff --git a/app/Services/Servers/EnvironmentService.php b/app/Services/Servers/EnvironmentService.php index 54b82bab32..8f45bab2da 100644 --- a/app/Services/Servers/EnvironmentService.php +++ b/app/Services/Servers/EnvironmentService.php @@ -7,16 +7,13 @@ class EnvironmentService { - /** - * @var array - */ - private $additional = []; + private array $additional = []; /** * Dynamically configure additional environment variables to be assigned * with a specific server. */ - public function setEnvironmentKey(string $key, callable $closure) + public function setEnvironmentKey(string $key, callable $closure): void { $this->additional[$key] = $closure; } diff --git a/app/Services/Servers/GetUserPermissionsService.php b/app/Services/Servers/GetUserPermissionsService.php index fdac03bfdf..ae91cb33ff 100644 --- a/app/Services/Servers/GetUserPermissionsService.php +++ b/app/Services/Servers/GetUserPermissionsService.php @@ -11,10 +11,8 @@ class GetUserPermissionsService * Returns the server specific permissions that a user has. This checks * if they are an admin or a subuser for the server. If no permissions are * found, an empty array is returned. - * - * @return string[] */ - public function handle(Server $server, User $user) + public function handle(Server $server, User $user): array { if ($user->root_admin || $user->id === $server->owner_id) { $permissions = ['*']; diff --git a/app/Services/Servers/ReinstallServerService.php b/app/Services/Servers/ReinstallServerService.php index 561512b379..5881d1fc63 100644 --- a/app/Services/Servers/ReinstallServerService.php +++ b/app/Services/Servers/ReinstallServerService.php @@ -8,35 +8,21 @@ class ReinstallServerService { - /** - * @var \Pterodactyl\Repositories\Wings\DaemonServerRepository - */ - private $daemonServerRepository; - - /** - * @var \Illuminate\Database\ConnectionInterface - */ - private $connection; - /** * ReinstallService constructor. */ public function __construct( - ConnectionInterface $connection, - DaemonServerRepository $daemonServerRepository + private ConnectionInterface $connection, + private DaemonServerRepository $daemonServerRepository ) { - $this->daemonServerRepository = $daemonServerRepository; - $this->connection = $connection; } /** * Reinstall a server on the remote daemon. * - * @return \Pterodactyl\Models\Server - * * @throws \Throwable */ - public function handle(Server $server) + public function handle(Server $server): Server { return $this->connection->transaction(function () use ($server) { $server->fill(['status' => Server::STATUS_INSTALLING])->save(); diff --git a/app/Services/Servers/ServerConfigurationStructureService.php b/app/Services/Servers/ServerConfigurationStructureService.php index 0625f68918..72f3277967 100644 --- a/app/Services/Servers/ServerConfigurationStructureService.php +++ b/app/Services/Servers/ServerConfigurationStructureService.php @@ -7,14 +7,11 @@ class ServerConfigurationStructureService { - private EnvironmentService $environment; - /** * ServerConfigurationStructureService constructor. */ - public function __construct(EnvironmentService $environment) + public function __construct(private EnvironmentService $environment) { - $this->environment = $environment; } /** diff --git a/app/Services/Servers/ServerCreationService.php b/app/Services/Servers/ServerCreationService.php index 48fa8cc334..2c9b4cf844 100644 --- a/app/Services/Servers/ServerCreationService.php +++ b/app/Services/Servers/ServerCreationService.php @@ -12,7 +12,6 @@ use Pterodactyl\Models\Allocation; use Illuminate\Database\ConnectionInterface; use Pterodactyl\Models\Objects\DeploymentObject; -use Pterodactyl\Repositories\Eloquent\EggRepository; use Pterodactyl\Repositories\Eloquent\ServerRepository; use Pterodactyl\Repositories\Wings\DaemonServerRepository; use Pterodactyl\Services\Deployment\FindViableNodesService; @@ -23,84 +22,18 @@ class ServerCreationService { /** - * @var \Pterodactyl\Services\Deployment\AllocationSelectionService - */ - private $allocationSelectionService; - - /** - * @var \Pterodactyl\Services\Servers\ServerConfigurationStructureService - */ - private $configurationStructureService; - - /** - * @var \Illuminate\Database\ConnectionInterface - */ - private $connection; - - /** - * @var \Pterodactyl\Services\Deployment\FindViableNodesService - */ - private $findViableNodesService; - - /** - * @var \Pterodactyl\Services\Servers\VariableValidatorService - */ - private $validatorService; - - /** - * @var \Pterodactyl\Repositories\Eloquent\EggRepository - */ - private $eggRepository; - - /** - * @var \Pterodactyl\Repositories\Eloquent\ServerRepository - */ - private $repository; - - /** - * @var \Pterodactyl\Repositories\Eloquent\ServerVariableRepository - */ - private $serverVariableRepository; - - /** - * @var \Pterodactyl\Repositories\Wings\DaemonServerRepository - */ - private $daemonServerRepository; - - /** - * @var \Pterodactyl\Services\Servers\ServerDeletionService - */ - private $serverDeletionService; - - /** - * CreationService constructor. - * - * @param \Pterodactyl\Services\Servers\ServerConfigurationStructureService $configurationStructureService - * @param \Pterodactyl\Services\Servers\ServerDeletionService $serverDeletionService - * @param \Pterodactyl\Services\Servers\VariableValidatorService $validatorService + * ServerCreationService constructor. */ public function __construct( - AllocationSelectionService $allocationSelectionService, - ConnectionInterface $connection, - DaemonServerRepository $daemonServerRepository, - EggRepository $eggRepository, - FindViableNodesService $findViableNodesService, - ServerConfigurationStructureService $configurationStructureService, - ServerDeletionService $serverDeletionService, - ServerRepository $repository, - ServerVariableRepository $serverVariableRepository, - VariableValidatorService $validatorService + private AllocationSelectionService $allocationSelectionService, + private ConnectionInterface $connection, + private DaemonServerRepository $daemonServerRepository, + private FindViableNodesService $findViableNodesService, + private ServerRepository $repository, + private ServerDeletionService $serverDeletionService, + private ServerVariableRepository $serverVariableRepository, + private VariableValidatorService $validatorService ) { - $this->allocationSelectionService = $allocationSelectionService; - $this->configurationStructureService = $configurationStructureService; - $this->connection = $connection; - $this->findViableNodesService = $findViableNodesService; - $this->validatorService = $validatorService; - $this->eggRepository = $eggRepository; - $this->repository = $repository; - $this->serverVariableRepository = $serverVariableRepository; - $this->daemonServerRepository = $daemonServerRepository; - $this->serverDeletionService = $serverDeletionService; } /** @@ -237,7 +170,7 @@ private function createModel(array $data): Server /** * Configure the allocations assigned to this server. */ - private function storeAssignedAllocations(Server $server, array $data) + private function storeAssignedAllocations(Server $server, array $data): void { $records = [$data['allocation_id']]; if (isset($data['allocation_additional']) && is_array($data['allocation_additional'])) { @@ -252,7 +185,7 @@ private function storeAssignedAllocations(Server $server, array $data) /** * Process environment variables passed for this server and store them in the database. */ - private function storeEggVariables(Server $server, Collection $variables) + private function storeEggVariables(Server $server, Collection $variables): void { $records = $variables->map(function ($result) use ($server) { return [ diff --git a/app/Services/Servers/ServerDeletionService.php b/app/Services/Servers/ServerDeletionService.php index cc973ff845..be52b0cf41 100644 --- a/app/Services/Servers/ServerDeletionService.php +++ b/app/Services/Servers/ServerDeletionService.php @@ -13,47 +13,22 @@ class ServerDeletionService { - /** - * @var bool - */ - protected $force = false; + protected bool $force = false; /** - * @var \Illuminate\Database\ConnectionInterface - */ - private $connection; - - /** - * @var \Pterodactyl\Repositories\Wings\DaemonServerRepository - */ - private $daemonServerRepository; - - /** - * @var \Pterodactyl\Services\Databases\DatabaseManagementService - */ - private $databaseManagementService; - - /** - * DeletionService constructor. + * ServerDeletionService constructor. */ public function __construct( - ConnectionInterface $connection, - DaemonServerRepository $daemonServerRepository, - DatabaseManagementService $databaseManagementService + private ConnectionInterface $connection, + private DaemonServerRepository $daemonServerRepository, + private DatabaseManagementService $databaseManagementService ) { - $this->connection = $connection; - $this->daemonServerRepository = $daemonServerRepository; - $this->databaseManagementService = $databaseManagementService; } /** * Set if the server should be forcibly deleted from the panel (ignoring daemon errors) or not. - * - * @param bool $bool - * - * @return $this */ - public function withForce($bool = true) + public function withForce(bool $bool = true): self { $this->force = $bool; @@ -66,7 +41,7 @@ public function withForce($bool = true) * @throws \Throwable * @throws \Pterodactyl\Exceptions\DisplayException */ - public function handle(Server $server) + public function handle(Server $server): void { try { $this->daemonServerRepository->setServer($server)->delete(); diff --git a/app/Services/Servers/StartupModificationService.php b/app/Services/Servers/StartupModificationService.php index 66645e4f2f..a0a5c4cb77 100644 --- a/app/Services/Servers/StartupModificationService.php +++ b/app/Services/Servers/StartupModificationService.php @@ -14,25 +14,11 @@ class StartupModificationService { use HasUserLevels; - /** - * @var \Illuminate\Database\ConnectionInterface - */ - private $connection; - - /** - * @var \Pterodactyl\Services\Servers\VariableValidatorService - */ - private $validatorService; - /** * StartupModificationService constructor. - * - * @param \Pterodactyl\Services\Servers\VariableValidatorService $validatorService */ - public function __construct(ConnectionInterface $connection, VariableValidatorService $validatorService) + public function __construct(private ConnectionInterface $connection, private VariableValidatorService $validatorService) { - $this->connection = $connection; - $this->validatorService = $validatorService; } /** @@ -79,7 +65,7 @@ public function handle(Server $server, array $data): Server /** * Update certain administrative settings for a server in the DB. */ - protected function updateAdministrativeSettings(array $data, Server &$server) + protected function updateAdministrativeSettings(array $data, Server &$server): void { $eggId = Arr::get($data, 'egg_id'); diff --git a/app/Services/Servers/SuspensionService.php b/app/Services/Servers/SuspensionService.php index 27bc622f5a..d11ace22c8 100644 --- a/app/Services/Servers/SuspensionService.php +++ b/app/Services/Servers/SuspensionService.php @@ -4,7 +4,6 @@ use Webmozart\Assert\Assert; use Pterodactyl\Models\Server; -use Illuminate\Database\ConnectionInterface; use Pterodactyl\Repositories\Wings\DaemonServerRepository; use Symfony\Component\HttpKernel\Exception\ConflictHttpException; @@ -13,42 +12,27 @@ class SuspensionService public const ACTION_SUSPEND = 'suspend'; public const ACTION_UNSUSPEND = 'unsuspend'; - /** - * @var \Illuminate\Database\ConnectionInterface - */ - private $connection; - - /** - * @var \Pterodactyl\Repositories\Wings\DaemonServerRepository - */ - private $daemonServerRepository; - /** * SuspensionService constructor. */ public function __construct( - ConnectionInterface $connection, - DaemonServerRepository $daemonServerRepository + private DaemonServerRepository $daemonServerRepository ) { - $this->connection = $connection; - $this->daemonServerRepository = $daemonServerRepository; } /** * Suspends a server on the system. * - * @param string $action - * * @throws \Throwable */ - public function toggle(Server $server, $action = self::ACTION_SUSPEND) + public function toggle(Server $server, string $action = self::ACTION_SUSPEND): void { Assert::oneOf($action, [self::ACTION_SUSPEND, self::ACTION_UNSUSPEND]); $isSuspending = $action === self::ACTION_SUSPEND; - // Nothing needs to happen if we're suspending the server and it is already + // Nothing needs to happen if we're suspending the server, and it is already // suspended in the database. Additionally, nothing needs to happen if the server - // is not suspended and we try to un-suspend the instance. + // is not suspended, and we try to un-suspend the instance. if ($isSuspending === $server->isSuspended()) { return; } diff --git a/app/Services/Servers/TransferService.php b/app/Services/Servers/TransferService.php index 35aed0659f..24ef0a5881 100644 --- a/app/Services/Servers/TransferService.php +++ b/app/Services/Servers/TransferService.php @@ -4,39 +4,23 @@ use Pterodactyl\Models\Server; use Pterodactyl\Repositories\Wings\DaemonServerRepository; -use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; class TransferService { - /** - * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface - */ - private $repository; - - /** - * @var \Pterodactyl\Repositories\Wings\DaemonServerRepository - */ - private $daemonServerRepository; - /** * TransferService constructor. */ public function __construct( - DaemonServerRepository $daemonServerRepository, - ServerRepositoryInterface $repository + private DaemonServerRepository $daemonServerRepository ) { - $this->repository = $repository; - $this->daemonServerRepository = $daemonServerRepository; } /** * Requests an archive from the daemon. * - * @param int|\Pterodactyl\Models\Server $server - * - * @throws \Throwable + * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException */ - public function requestArchive(Server $server) + public function requestArchive(Server $server): void { $this->daemonServerRepository->setServer($server)->requestArchive(); } diff --git a/app/Services/Servers/VariableValidatorService.php b/app/Services/Servers/VariableValidatorService.php index 2ac3879a51..a198692653 100644 --- a/app/Services/Servers/VariableValidatorService.php +++ b/app/Services/Servers/VariableValidatorService.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Services\Servers; @@ -20,17 +13,11 @@ class VariableValidatorService { use HasUserLevels; - /** - * @var \Illuminate\Contracts\Validation\Factory - */ - private $validator; - /** * VariableValidatorService constructor. */ - public function __construct(ValidationFactory $validator) + public function __construct(private ValidationFactory $validator) { - $this->validator = $validator; } /** @@ -42,7 +29,7 @@ public function handle(int $egg, array $fields = []): Collection { $query = EggVariable::query()->where('egg_id', $egg); if (!$this->isUserLevel(User::USER_LEVEL_ADMIN)) { - // Don't attempt to validate variables if they aren't user editable + // Don't attempt to validate variables if they aren't user editable, // and we're not running this at an admin level. $query = $query->where('user_editable', true)->where('user_viewable', true); } diff --git a/app/Services/Subusers/SubuserCreationService.php b/app/Services/Subusers/SubuserCreationService.php index b1affd3f38..c207b0d071 100644 --- a/app/Services/Subusers/SubuserCreationService.php +++ b/app/Services/Subusers/SubuserCreationService.php @@ -15,39 +15,15 @@ class SubuserCreationService { - /** - * @var \Illuminate\Database\ConnectionInterface - */ - private $connection; - - /** - * @var \Pterodactyl\Repositories\Eloquent\SubuserRepository - */ - private $subuserRepository; - - /** - * @var \Pterodactyl\Services\Users\UserCreationService - */ - private $userCreationService; - - /** - * @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface - */ - private $userRepository; - /** * SubuserCreationService constructor. */ public function __construct( - ConnectionInterface $connection, - SubuserRepository $subuserRepository, - UserCreationService $userCreationService, - UserRepositoryInterface $userRepository + private ConnectionInterface $connection, + private SubuserRepository $subuserRepository, + private UserCreationService $userCreationService, + private UserRepositoryInterface $userRepository ) { - $this->connection = $connection; - $this->subuserRepository = $subuserRepository; - $this->userRepository = $userRepository; - $this->userCreationService = $userCreationService; } /** @@ -74,7 +50,7 @@ public function handle(Server $server, string $email, array $permissions): Subus if ($subuserCount !== 0) { throw new ServerSubuserExistsException(trans('exceptions.subusers.subuser_exists')); } - } catch (RecordNotFoundException $exception) { + } catch (RecordNotFoundException) { // Just cap the username generated at 64 characters at most and then append a random string // to the end to make it "unique"... $username = substr(preg_replace('/([^\w\.-]+)/', '', strtok($email, '@')), 0, 64) . Str::random(3); diff --git a/app/Services/Users/ToggleTwoFactorService.php b/app/Services/Users/ToggleTwoFactorService.php index 28faff7f1b..a91615b1ce 100644 --- a/app/Services/Users/ToggleTwoFactorService.php +++ b/app/Services/Users/ToggleTwoFactorService.php @@ -14,53 +14,21 @@ class ToggleTwoFactorService { - /** - * @var \Illuminate\Contracts\Encryption\Encrypter - */ - private $encrypter; - - /** - * @var \PragmaRX\Google2FA\Google2FA - */ - private $google2FA; - - /** - * @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface - */ - private $repository; - - /** - * @var \Pterodactyl\Repositories\Eloquent\RecoveryTokenRepository - */ - private $recoveryTokenRepository; - - /** - * @var \Illuminate\Database\ConnectionInterface - */ - private $connection; - /** * ToggleTwoFactorService constructor. */ public function __construct( - ConnectionInterface $connection, - Encrypter $encrypter, - Google2FA $google2FA, - RecoveryTokenRepository $recoveryTokenRepository, - UserRepositoryInterface $repository + private ConnectionInterface $connection, + private Encrypter $encrypter, + private Google2FA $google2FA, + private RecoveryTokenRepository $recoveryTokenRepository, + private UserRepositoryInterface $repository ) { - $this->encrypter = $encrypter; - $this->google2FA = $google2FA; - $this->repository = $repository; - $this->recoveryTokenRepository = $recoveryTokenRepository; - $this->connection = $connection; } /** * Toggle 2FA on an account only if the token provided is valid. * - * @return string[] - * * @throws \Throwable * @throws \PragmaRX\Google2FA\Exceptions\IncompatibleWithGoogleAuthenticatorException * @throws \PragmaRX\Google2FA\Exceptions\InvalidCharactersException diff --git a/app/Services/Users/TwoFactorSetupService.php b/app/Services/Users/TwoFactorSetupService.php index be234d4678..87f1e54433 100644 --- a/app/Services/Users/TwoFactorSetupService.php +++ b/app/Services/Users/TwoFactorSetupService.php @@ -13,32 +13,14 @@ class TwoFactorSetupService { public const VALID_BASE32_CHARACTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'; - /** - * @var \Illuminate\Contracts\Config\Repository - */ - private $config; - - /** - * @var \Illuminate\Contracts\Encryption\Encrypter - */ - private $encrypter; - - /** - * @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface - */ - private $repository; - /** * TwoFactorSetupService constructor. */ public function __construct( - ConfigRepository $config, - Encrypter $encrypter, - UserRepositoryInterface $repository + private ConfigRepository $config, + private Encrypter $encrypter, + private UserRepositoryInterface $repository ) { - $this->config = $config; - $this->encrypter = $encrypter; - $this->repository = $repository; } /** diff --git a/app/Services/Users/UserCreationService.php b/app/Services/Users/UserCreationService.php index 2d0abcb9fa..130ae1a4e2 100644 --- a/app/Services/Users/UserCreationService.php +++ b/app/Services/Users/UserCreationService.php @@ -3,6 +3,7 @@ namespace Pterodactyl\Services\Users; use Ramsey\Uuid\Uuid; +use Pterodactyl\Models\User; use Illuminate\Contracts\Hashing\Hasher; use Illuminate\Database\ConnectionInterface; use Illuminate\Contracts\Auth\PasswordBroker; @@ -12,49 +13,23 @@ class UserCreationService { /** - * @var \Illuminate\Database\ConnectionInterface - */ - private $connection; - - /** - * @var \Illuminate\Contracts\Hashing\Hasher - */ - private $hasher; - - /** - * @var \Illuminate\Contracts\Auth\PasswordBroker - */ - private $passwordBroker; - - /** - * @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface - */ - private $repository; - - /** - * CreationService constructor. + * UserCreationService constructor. */ public function __construct( - ConnectionInterface $connection, - Hasher $hasher, - PasswordBroker $passwordBroker, - UserRepositoryInterface $repository + private ConnectionInterface $connection, + private Hasher $hasher, + private PasswordBroker $passwordBroker, + private UserRepositoryInterface $repository ) { - $this->connection = $connection; - $this->hasher = $hasher; - $this->passwordBroker = $passwordBroker; - $this->repository = $repository; } /** * Create a new user on the system. * - * @return \Pterodactyl\Models\User - * * @throws \Exception * @throws \Pterodactyl\Exceptions\Model\DataValidationException */ - public function handle(array $data) + public function handle(array $data): User { if (array_key_exists('password', $data) && !empty($data['password'])) { $data['password'] = $this->hasher->make($data['password']); diff --git a/app/Services/Users/UserDeletionService.php b/app/Services/Users/UserDeletionService.php index fe02bc72c9..f7f060cee4 100644 --- a/app/Services/Users/UserDeletionService.php +++ b/app/Services/Users/UserDeletionService.php @@ -1,13 +1,5 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - namespace Pterodactyl\Services\Users; use Pterodactyl\Models\User; @@ -19,43 +11,21 @@ class UserDeletionService { /** - * @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface - */ - protected $repository; - - /** - * @var \Illuminate\Contracts\Translation\Translator - */ - protected $translator; - - /** - * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface - */ - protected $serverRepository; - - /** - * DeletionService constructor. + * UserDeletionService constructor. */ public function __construct( - ServerRepositoryInterface $serverRepository, - Translator $translator, - UserRepositoryInterface $repository + protected UserRepositoryInterface $repository, + protected ServerRepositoryInterface $serverRepository, + protected Translator $translator ) { - $this->repository = $repository; - $this->translator = $translator; - $this->serverRepository = $serverRepository; } /** * Delete a user from the panel only if they have no servers attached to their account. * - * @param int|\Pterodactyl\Models\User $user - * - * @return bool|null - * * @throws \Pterodactyl\Exceptions\DisplayException */ - public function handle($user) + public function handle(int|User $user): ?bool { if ($user instanceof User) { $user = $user->id; diff --git a/app/Services/Users/UserUpdateService.php b/app/Services/Users/UserUpdateService.php index 31f4010b65..770fbdf0b4 100644 --- a/app/Services/Users/UserUpdateService.php +++ b/app/Services/Users/UserUpdateService.php @@ -11,16 +11,10 @@ class UserUpdateService use HasUserLevels; /** - * @var \Illuminate\Contracts\Hashing\Hasher + * UserUpdateService constructor. */ - private $hasher; - - /** - * UpdateService constructor. - */ - public function __construct(Hasher $hasher) + public function __construct(private Hasher $hasher) { - $this->hasher = $hasher; } /** diff --git a/app/Traits/Commands/EnvironmentWriterTrait.php b/app/Traits/Commands/EnvironmentWriterTrait.php index 5435dc3214..3d1aa08999 100644 --- a/app/Traits/Commands/EnvironmentWriterTrait.php +++ b/app/Traits/Commands/EnvironmentWriterTrait.php @@ -8,7 +8,7 @@ trait EnvironmentWriterTrait { /** * Escapes an environment value by looking for any characters that could - * reasonablly cause environment parsing issues. Those values are then wrapped + * reasonably cause environment parsing issues. Those values are then wrapped * in quotes before being returned. */ public function escapeEnvironmentValue(string $value): string @@ -25,7 +25,7 @@ public function escapeEnvironmentValue(string $value): string * * @throws \Pterodactyl\Exceptions\PterodactylException */ - public function writeToEnvironment(array $values = []) + public function writeToEnvironment(array $values = []): void { $path = base_path('.env'); if (!file_exists($path)) { diff --git a/app/Traits/Controllers/JavascriptInjection.php b/app/Traits/Controllers/JavascriptInjection.php index 4e7e0a1205..206d5ed5ff 100644 --- a/app/Traits/Controllers/JavascriptInjection.php +++ b/app/Traits/Controllers/JavascriptInjection.php @@ -1,30 +1,18 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Traits\Controllers; -use Javascript; +use JavaScript; use Illuminate\Http\Request; trait JavascriptInjection { - /** - * @var \Illuminate\Http\Request - */ - private $request; + private Request $request; /** * Set the request object to use when injecting JS. - * - * @return $this */ - public function setRequest(Request $request) + public function setRequest(Request $request): self { $this->request = $request; @@ -33,13 +21,9 @@ public function setRequest(Request $request) /** * Injects the exact array passed in, nothing more. - * - * @param array $args - * - * @return array */ - public function plainInject($args = []) + public function plainInject(array $args = []): string { - return Javascript::put($args); + return JavaScript::put($args); } } diff --git a/app/Traits/Controllers/PlainJavascriptInjection.php b/app/Traits/Controllers/PlainJavascriptInjection.php index f514eb1af6..eb2f6559f6 100644 --- a/app/Traits/Controllers/PlainJavascriptInjection.php +++ b/app/Traits/Controllers/PlainJavascriptInjection.php @@ -1,10 +1,4 @@ getFilesystemInstance()->directories(resource_path('lang')))->mapWithKeys(function ($path) use ($localize) { $code = basename($path); diff --git a/app/Traits/Services/HasUserLevels.php b/app/Traits/Services/HasUserLevels.php index 293e7fce74..33be0fbf1a 100644 --- a/app/Traits/Services/HasUserLevels.php +++ b/app/Traits/Services/HasUserLevels.php @@ -6,17 +6,12 @@ trait HasUserLevels { - /** - * @var int - */ - private $userLevel = User::USER_LEVEL_USER; + private int $userLevel = User::USER_LEVEL_USER; /** * Set the access level for running this function. - * - * @return $this */ - public function setUserLevel(int $level) + public function setUserLevel(int $level): self { $this->userLevel = $level; diff --git a/app/Traits/Services/ReturnsUpdatedModels.php b/app/Traits/Services/ReturnsUpdatedModels.php index 07e4f4fde3..055e8971ed 100644 --- a/app/Traits/Services/ReturnsUpdatedModels.php +++ b/app/Traits/Services/ReturnsUpdatedModels.php @@ -4,15 +4,9 @@ trait ReturnsUpdatedModels { - /** - * @var bool - */ - private $updatedModel = false; + private bool $updatedModel = false; - /** - * @return bool - */ - public function getUpdatedModel() + public function getUpdatedModel(): bool { return $this->updatedModel; } @@ -21,10 +15,8 @@ public function getUpdatedModel() * If called a fresh model will be returned from the database. This is used * for API calls, but is unnecessary for UI based updates where the page is * being reloaded and a fresh model will be pulled anyways. - * - * @return $this */ - public function returnUpdatedModel(bool $toggle = true) + public function returnUpdatedModel(bool $toggle = true): self { $this->updatedModel = $toggle; diff --git a/app/Traits/Services/ValidatesValidationRules.php b/app/Traits/Services/ValidatesValidationRules.php index f4c1ea1881..32265da741 100644 --- a/app/Traits/Services/ValidatesValidationRules.php +++ b/app/Traits/Services/ValidatesValidationRules.php @@ -4,22 +4,20 @@ use BadMethodCallException; use Illuminate\Support\Str; -use Illuminate\Contracts\Validation\Factory; +use Illuminate\Contracts\Validation\Factory as ValidationFactory; use Pterodactyl\Exceptions\Service\Egg\Variable\BadValidationRuleException; trait ValidatesValidationRules { - abstract protected function getValidator(): Factory; + abstract protected function getValidator(): ValidationFactory; /** * Validate that the rules being provided are valid for Laravel and can * be resolved. * - * @param array|string $rules - * * @throws \Pterodactyl\Exceptions\Service\Egg\Variable\BadValidationRuleException */ - public function validateRules($rules) + public function validateRules(array|string $rules): void { try { $this->getValidator()->make(['__TEST' => 'test'], ['__TEST' => $rules])->fails(); diff --git a/app/Transformers/Api/Application/AllocationTransformer.php b/app/Transformers/Api/Application/AllocationTransformer.php index 3584d86835..fcd65f98f0 100644 --- a/app/Transformers/Api/Application/AllocationTransformer.php +++ b/app/Transformers/Api/Application/AllocationTransformer.php @@ -4,7 +4,9 @@ use Pterodactyl\Models\Node; use Pterodactyl\Models\Server; +use League\Fractal\Resource\Item; use Pterodactyl\Models\Allocation; +use League\Fractal\Resource\NullResource; use Pterodactyl\Services\Acl\Api\AdminAcl; class AllocationTransformer extends BaseTransformer @@ -24,10 +26,8 @@ public function getResourceName(): string /** * Return a generic transformed allocation array. - * - * @return array */ - public function transform(Allocation $allocation) + public function transform(Allocation $allocation): array { return [ 'id' => $allocation->id, @@ -42,11 +42,9 @@ public function transform(Allocation $allocation) /** * Load the node relationship onto a given transformation. * - * @return \League\Fractal\Resource\Item|\League\Fractal\Resource\NullResource - * * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ - public function includeNode(Allocation $allocation) + public function includeNode(Allocation $allocation): Item|NullResource { if (!$this->authorize(AdminAcl::RESOURCE_NODES)) { return $this->null(); @@ -62,11 +60,9 @@ public function includeNode(Allocation $allocation) /** * Load the server relationship onto a given transformation. * - * @return \League\Fractal\Resource\Item|\League\Fractal\Resource\NullResource - * * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ - public function includeServer(Allocation $allocation) + public function includeServer(Allocation $allocation): Item|NullResource { if (!$this->authorize(AdminAcl::RESOURCE_SERVERS) || !$allocation->server) { return $this->null(); diff --git a/app/Transformers/Api/Application/BaseTransformer.php b/app/Transformers/Api/Application/BaseTransformer.php index dce0b14424..723caa3b8b 100644 --- a/app/Transformers/Api/Application/BaseTransformer.php +++ b/app/Transformers/Api/Application/BaseTransformer.php @@ -3,6 +3,7 @@ namespace Pterodactyl\Transformers\Api\Application; use Carbon\CarbonImmutable; +use Carbon\CarbonInterface; use Illuminate\Http\Request; use Webmozart\Assert\Assert; use Pterodactyl\Models\ApiKey; @@ -38,8 +39,6 @@ abstract public function getResourceName(): string; /** * Sets the request on the instance. - * - * @return static */ public function setRequest(Request $request): self { @@ -50,10 +49,8 @@ public function setRequest(Request $request): self /** * Returns a new transformer instance with the request set on the instance. - * - * @return \Pterodactyl\Transformers\Api\Application\BaseTransformer */ - public static function fromRequest(Request $request) + public static function fromRequest(Request $request): BaseTransformer { return app(static::class)->setRequest($request); } @@ -81,7 +78,7 @@ protected function authorize(string $resource): bool return $this->request->user()->root_admin; } - return AdminAcl::check($token, $resource, AdminAcl::READ); + return AdminAcl::check($token, $resource); } /** @@ -96,7 +93,6 @@ protected function authorize(string $resource): bool * * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException * - * @noinspection PhpUndefinedClassInspection * @noinspection PhpDocSignatureInspection */ protected function makeTransformer(string $abstract) @@ -111,8 +107,8 @@ protected function makeTransformer(string $abstract) */ protected function formatTimestamp(string $timestamp): string { - return CarbonImmutable::createFromFormat(CarbonImmutable::DEFAULT_TO_STRING_FORMAT, $timestamp) + return CarbonImmutable::createFromFormat(CarbonInterface::DEFAULT_TO_STRING_FORMAT, $timestamp) ->setTimezone(self::RESPONSE_TIMEZONE) - ->toIso8601String(); + ->toAtomString(); } } diff --git a/app/Transformers/Api/Application/DatabaseHostTransformer.php b/app/Transformers/Api/Application/DatabaseHostTransformer.php index 08d635a033..019fdf261d 100644 --- a/app/Transformers/Api/Application/DatabaseHostTransformer.php +++ b/app/Transformers/Api/Application/DatabaseHostTransformer.php @@ -4,6 +4,8 @@ use Pterodactyl\Models\Database; use Pterodactyl\Models\DatabaseHost; +use League\Fractal\Resource\Collection; +use League\Fractal\Resource\NullResource; use Pterodactyl\Services\Acl\Api\AdminAcl; class DatabaseHostTransformer extends BaseTransformer @@ -22,10 +24,8 @@ public function getResourceName(): string /** * Transform database host into a representation for the application API. - * - * @return array */ - public function transform(DatabaseHost $model) + public function transform(DatabaseHost $model): array { return [ 'id' => $model->id, @@ -34,19 +34,17 @@ public function transform(DatabaseHost $model) 'port' => $model->port, 'username' => $model->username, 'node' => $model->node_id, - 'created_at' => $model->created_at->toIso8601String(), - 'updated_at' => $model->updated_at->toIso8601String(), + 'created_at' => $model->created_at->toAtomString(), + 'updated_at' => $model->updated_at->toAtomString(), ]; } /** * Include the databases associated with this host. * - * @return \League\Fractal\Resource\Collection|\League\Fractal\Resource\NullResource - * * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ - public function includeDatabases(DatabaseHost $model) + public function includeDatabases(DatabaseHost $model): Collection|NullResource { if (!$this->authorize(AdminAcl::RESOURCE_SERVER_DATABASES)) { return $this->null(); diff --git a/app/Transformers/Api/Application/EggTransformer.php b/app/Transformers/Api/Application/EggTransformer.php index 7215d6d4af..9ed5736b46 100644 --- a/app/Transformers/Api/Application/EggTransformer.php +++ b/app/Transformers/Api/Application/EggTransformer.php @@ -6,7 +6,10 @@ use Pterodactyl\Models\Egg; use Pterodactyl\Models\Nest; use Pterodactyl\Models\Server; +use League\Fractal\Resource\Item; use Pterodactyl\Models\EggVariable; +use League\Fractal\Resource\Collection; +use League\Fractal\Resource\NullResource; use Pterodactyl\Services\Acl\Api\AdminAcl; class EggTransformer extends BaseTransformer @@ -34,9 +37,9 @@ public function getResourceName(): string * Transform an Egg model into a representation that can be consumed by * the application api. * - * @return array + * @throws \JsonException */ - public function transform(Egg $model) + public function transform(Egg $model): array { $files = json_decode($model->config_files, true, 512, JSON_THROW_ON_ERROR); if (empty($files)) { @@ -79,11 +82,9 @@ public function transform(Egg $model) /** * Include the Nest relationship for the given Egg in the transformation. * - * @return \League\Fractal\Resource\Item|\League\Fractal\Resource\NullResource - * * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ - public function includeNest(Egg $model) + public function includeNest(Egg $model): Item|NullResource { if (!$this->authorize(AdminAcl::RESOURCE_NESTS)) { return $this->null(); @@ -97,11 +98,9 @@ public function includeNest(Egg $model) /** * Include the Servers relationship for the given Egg in the transformation. * - * @return \League\Fractal\Resource\Collection|\League\Fractal\Resource\NullResource - * * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ - public function includeServers(Egg $model) + public function includeServers(Egg $model): Collection|NullResource { if (!$this->authorize(AdminAcl::RESOURCE_SERVERS)) { return $this->null(); @@ -115,10 +114,8 @@ public function includeServers(Egg $model) /** * Include more detailed information about the configuration if this Egg is * extending another. - * - * @return \League\Fractal\Resource\Item|\League\Fractal\Resource\NullResource */ - public function includeConfig(Egg $model) + public function includeConfig(Egg $model): Item|NullResource { if (is_null($model->config_from)) { return $this->null(); @@ -139,10 +136,8 @@ public function includeConfig(Egg $model) /** * Include more detailed information about the script configuration if the * Egg is extending another. - * - * @return \League\Fractal\Resource\Item|\League\Fractal\Resource\NullResource */ - public function includeScript(Egg $model) + public function includeScript(Egg $model): Item|NullResource { if (is_null($model->copy_script_from)) { return $this->null(); @@ -163,11 +158,9 @@ public function includeScript(Egg $model) /** * Include the variables that are defined for this Egg. * - * @return \League\Fractal\Resource\Collection|\League\Fractal\Resource\NullResource - * * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ - public function includeVariables(Egg $model) + public function includeVariables(Egg $model): Collection|NullResource { if (!$this->authorize(AdminAcl::RESOURCE_EGGS)) { return $this->null(); diff --git a/app/Transformers/Api/Application/LocationTransformer.php b/app/Transformers/Api/Application/LocationTransformer.php index 90564d81df..8fea3feb31 100644 --- a/app/Transformers/Api/Application/LocationTransformer.php +++ b/app/Transformers/Api/Application/LocationTransformer.php @@ -3,6 +3,8 @@ namespace Pterodactyl\Transformers\Api\Application; use Pterodactyl\Models\Location; +use League\Fractal\Resource\Collection; +use League\Fractal\Resource\NullResource; use Pterodactyl\Services\Acl\Api\AdminAcl; class LocationTransformer extends BaseTransformer @@ -37,11 +39,9 @@ public function transform(Location $location): array /** * Return the nodes associated with this location. * - * @return \League\Fractal\Resource\Collection|\League\Fractal\Resource\NullResource - * * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ - public function includeServers(Location $location) + public function includeServers(Location $location): Collection|NullResource { if (!$this->authorize(AdminAcl::RESOURCE_SERVERS)) { return $this->null(); @@ -55,11 +55,9 @@ public function includeServers(Location $location) /** * Return the nodes associated with this location. * - * @return \League\Fractal\Resource\Collection|\League\Fractal\Resource\NullResource - * * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ - public function includeNodes(Location $location) + public function includeNodes(Location $location): Collection|NullResource { if (!$this->authorize(AdminAcl::RESOURCE_NODES)) { return $this->null(); diff --git a/app/Transformers/Api/Application/NestTransformer.php b/app/Transformers/Api/Application/NestTransformer.php index 1274901a7c..2f530d44e4 100644 --- a/app/Transformers/Api/Application/NestTransformer.php +++ b/app/Transformers/Api/Application/NestTransformer.php @@ -5,6 +5,8 @@ use Pterodactyl\Models\Egg; use Pterodactyl\Models\Nest; use Pterodactyl\Models\Server; +use League\Fractal\Resource\Collection; +use League\Fractal\Resource\NullResource; use Pterodactyl\Services\Acl\Api\AdminAcl; class NestTransformer extends BaseTransformer @@ -27,10 +29,8 @@ public function getResourceName(): string /** * Transform a Nest model into a representation that can be consumed by the * application API. - * - * @return array */ - public function transform(Nest $model) + public function transform(Nest $model): array { $response = $model->toArray(); @@ -43,11 +43,9 @@ public function transform(Nest $model) /** * Include the Eggs relationship on the given Nest model transformation. * - * @return \League\Fractal\Resource\Collection|\League\Fractal\Resource\NullResource - * * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ - public function includeEggs(Nest $model) + public function includeEggs(Nest $model): Collection|NullResource { if (!$this->authorize(AdminAcl::RESOURCE_EGGS)) { return $this->null(); @@ -61,11 +59,9 @@ public function includeEggs(Nest $model) /** * Include the servers relationship on the given Nest model. * - * @return \League\Fractal\Resource\Collection|\League\Fractal\Resource\NullResource - * * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ - public function includeServers(Nest $model) + public function includeServers(Nest $model): Collection|NullResource { if (!$this->authorize(AdminAcl::RESOURCE_SERVERS)) { return $this->null(); diff --git a/app/Transformers/Api/Application/NodeTransformer.php b/app/Transformers/Api/Application/NodeTransformer.php index c7c1fc93b5..6347dfec39 100644 --- a/app/Transformers/Api/Application/NodeTransformer.php +++ b/app/Transformers/Api/Application/NodeTransformer.php @@ -3,6 +3,9 @@ namespace Pterodactyl\Transformers\Api\Application; use Pterodactyl\Models\Node; +use League\Fractal\Resource\Item; +use League\Fractal\Resource\Collection; +use League\Fractal\Resource\NullResource; use Pterodactyl\Services\Acl\Api\AdminAcl; class NodeTransformer extends BaseTransformer @@ -50,11 +53,9 @@ public function transform(Node $node): array /** * Return the nodes associated with this location. * - * @return \League\Fractal\Resource\Collection|\League\Fractal\Resource\NullResource - * * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ - public function includeAllocations(Node $node) + public function includeAllocations(Node $node): Collection|NullResource { if (!$this->authorize(AdminAcl::RESOURCE_ALLOCATIONS)) { return $this->null(); @@ -72,11 +73,9 @@ public function includeAllocations(Node $node) /** * Return the nodes associated with this location. * - * @return \League\Fractal\Resource\Item|\League\Fractal\Resource\NullResource - * * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ - public function includeLocation(Node $node) + public function includeLocation(Node $node): Item|NullResource { if (!$this->authorize(AdminAcl::RESOURCE_LOCATIONS)) { return $this->null(); @@ -94,11 +93,9 @@ public function includeLocation(Node $node) /** * Return the nodes associated with this location. * - * @return \League\Fractal\Resource\Collection|\League\Fractal\Resource\NullResource - * * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ - public function includeServers(Node $node) + public function includeServers(Node $node): Collection|NullResource { if (!$this->authorize(AdminAcl::RESOURCE_SERVERS)) { return $this->null(); diff --git a/app/Transformers/Api/Application/ServerDatabaseTransformer.php b/app/Transformers/Api/Application/ServerDatabaseTransformer.php index 4e425c9e4c..2590482d92 100644 --- a/app/Transformers/Api/Application/ServerDatabaseTransformer.php +++ b/app/Transformers/Api/Application/ServerDatabaseTransformer.php @@ -3,7 +3,9 @@ namespace Pterodactyl\Transformers\Api\Application; use Pterodactyl\Models\Database; +use League\Fractal\Resource\Item; use Pterodactyl\Models\DatabaseHost; +use League\Fractal\Resource\NullResource; use Pterodactyl\Services\Acl\Api\AdminAcl; use Illuminate\Contracts\Encryption\Encrypter; @@ -11,10 +13,7 @@ class ServerDatabaseTransformer extends BaseTransformer { protected array $availableIncludes = ['password', 'host']; - /** - * @var Encrypter - */ - private $encrypter; + private Encrypter $encrypter; /** * Perform dependency injection. @@ -45,17 +44,15 @@ public function transform(Database $model): array 'username' => $model->username, 'remote' => $model->remote, 'max_connections' => $model->max_connections, - 'created_at' => $model->created_at->toIso8601String(), - 'updated_at' => $model->updated_at->toIso8601String(), + 'created_at' => $model->created_at->toAtomString(), + 'updated_at' => $model->updated_at->toAtomString(), ]; } /** * Include the database password in the request. - * - * @return \League\Fractal\Resource\Item */ - public function includePassword(Database $model) + public function includePassword(Database $model): Item { return $this->item($model, function (Database $model) { return [ @@ -67,11 +64,9 @@ public function includePassword(Database $model) /** * Return the database host relationship for this server database. * - * @return \League\Fractal\Resource\Item|\League\Fractal\Resource\NullResource - * * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ - public function includeHost(Database $model) + public function includeHost(Database $model): Item|NullResource { if (!$this->authorize(AdminAcl::RESOURCE_DATABASE_HOSTS)) { return $this->null(); diff --git a/app/Transformers/Api/Application/ServerTransformer.php b/app/Transformers/Api/Application/ServerTransformer.php index 33110ec7eb..e5db01fb21 100644 --- a/app/Transformers/Api/Application/ServerTransformer.php +++ b/app/Transformers/Api/Application/ServerTransformer.php @@ -3,15 +3,15 @@ namespace Pterodactyl\Transformers\Api\Application; use Pterodactyl\Models\Server; +use League\Fractal\Resource\Item; +use League\Fractal\Resource\Collection; +use League\Fractal\Resource\NullResource; use Pterodactyl\Services\Acl\Api\AdminAcl; use Pterodactyl\Services\Servers\EnvironmentService; class ServerTransformer extends BaseTransformer { - /** - * @var \Pterodactyl\Services\Servers\EnvironmentService - */ - private $environmentService; + private EnvironmentService $environmentService; /** * List of resources that can be included. @@ -94,11 +94,9 @@ public function transform(Server $server): array /** * Return a generic array of allocations for this server. * - * @return \League\Fractal\Resource\Collection|\League\Fractal\Resource\NullResource - * * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ - public function includeAllocations(Server $server) + public function includeAllocations(Server $server): Collection|NullResource { if (!$this->authorize(AdminAcl::RESOURCE_ALLOCATIONS)) { return $this->null(); @@ -112,11 +110,9 @@ public function includeAllocations(Server $server) /** * Return a generic array of data about subusers for this server. * - * @return \League\Fractal\Resource\Collection|\League\Fractal\Resource\NullResource - * * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ - public function includeSubusers(Server $server) + public function includeSubusers(Server $server): Collection|NullResource { if (!$this->authorize(AdminAcl::RESOURCE_USERS)) { return $this->null(); @@ -130,11 +126,9 @@ public function includeSubusers(Server $server) /** * Return a generic array of data about subusers for this server. * - * @return \League\Fractal\Resource\Item|\League\Fractal\Resource\NullResource - * * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ - public function includeUser(Server $server) + public function includeUser(Server $server): Item|NullResource { if (!$this->authorize(AdminAcl::RESOURCE_USERS)) { return $this->null(); @@ -148,11 +142,9 @@ public function includeUser(Server $server) /** * Return a generic array with nest information for this server. * - * @return \League\Fractal\Resource\Item|\League\Fractal\Resource\NullResource - * * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ - public function includeNest(Server $server) + public function includeNest(Server $server): Item|NullResource { if (!$this->authorize(AdminAcl::RESOURCE_NESTS)) { return $this->null(); @@ -166,11 +158,9 @@ public function includeNest(Server $server) /** * Return a generic array with egg information for this server. * - * @return \League\Fractal\Resource\Item|\League\Fractal\Resource\NullResource - * * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ - public function includeEgg(Server $server) + public function includeEgg(Server $server): Item|NullResource { if (!$this->authorize(AdminAcl::RESOURCE_EGGS)) { return $this->null(); @@ -184,11 +174,9 @@ public function includeEgg(Server $server) /** * Return a generic array of data about subusers for this server. * - * @return \League\Fractal\Resource\Collection|\League\Fractal\Resource\NullResource - * * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ - public function includeVariables(Server $server) + public function includeVariables(Server $server): Collection|NullResource { if (!$this->authorize(AdminAcl::RESOURCE_SERVERS)) { return $this->null(); @@ -202,11 +190,9 @@ public function includeVariables(Server $server) /** * Return a generic array with location information for this server. * - * @return \League\Fractal\Resource\Item|\League\Fractal\Resource\NullResource - * * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ - public function includeLocation(Server $server) + public function includeLocation(Server $server): Item|NullResource { if (!$this->authorize(AdminAcl::RESOURCE_LOCATIONS)) { return $this->null(); @@ -220,11 +206,9 @@ public function includeLocation(Server $server) /** * Return a generic array with node information for this server. * - * @return \League\Fractal\Resource\Item|\League\Fractal\Resource\NullResource - * * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ - public function includeNode(Server $server) + public function includeNode(Server $server): Item|NullResource { if (!$this->authorize(AdminAcl::RESOURCE_NODES)) { return $this->null(); @@ -238,11 +222,9 @@ public function includeNode(Server $server) /** * Return a generic array with database information for this server. * - * @return \League\Fractal\Resource\Collection|\League\Fractal\Resource\NullResource - * * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ - public function includeDatabases(Server $server) + public function includeDatabases(Server $server): Collection|NullResource { if (!$this->authorize(AdminAcl::RESOURCE_SERVER_DATABASES)) { return $this->null(); diff --git a/app/Transformers/Api/Application/ServerVariableTransformer.php b/app/Transformers/Api/Application/ServerVariableTransformer.php index 2b1d68876d..25e8f879bd 100644 --- a/app/Transformers/Api/Application/ServerVariableTransformer.php +++ b/app/Transformers/Api/Application/ServerVariableTransformer.php @@ -2,7 +2,9 @@ namespace Pterodactyl\Transformers\Api\Application; +use League\Fractal\Resource\Item; use Pterodactyl\Models\EggVariable; +use League\Fractal\Resource\NullResource; use Pterodactyl\Services\Acl\Api\AdminAcl; class ServerVariableTransformer extends BaseTransformer @@ -22,10 +24,8 @@ public function getResourceName(): string /** * Return a generic transformed server variable array. - * - * @return array */ - public function transform(EggVariable $variable) + public function transform(EggVariable $variable): array { return $variable->toArray(); } @@ -33,11 +33,9 @@ public function transform(EggVariable $variable) /** * Return the parent service variable data. * - * @return \League\Fractal\Resource\Item|\League\Fractal\Resource\NullResource - * * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ - public function includeParent(EggVariable $variable) + public function includeParent(EggVariable $variable): Item|NullResource { if (!$this->authorize(AdminAcl::RESOURCE_EGGS)) { return $this->null(); diff --git a/app/Transformers/Api/Application/SubuserTransformer.php b/app/Transformers/Api/Application/SubuserTransformer.php index c7b8abd9e4..0a51d61d99 100644 --- a/app/Transformers/Api/Application/SubuserTransformer.php +++ b/app/Transformers/Api/Application/SubuserTransformer.php @@ -3,6 +3,8 @@ namespace Pterodactyl\Transformers\Api\Application; use Pterodactyl\Models\Subuser; +use League\Fractal\Resource\Item; +use League\Fractal\Resource\NullResource; use Pterodactyl\Services\Acl\Api\AdminAcl; class SubuserTransformer extends BaseTransformer @@ -38,11 +40,9 @@ public function transform(Subuser $subuser): array /** * Return a generic item of user for this subuser. * - * @return \League\Fractal\Resource\Item|\League\Fractal\Resource\NullResource - * * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ - public function includeUser(Subuser $subuser) + public function includeUser(Subuser $subuser): Item|NullResource { if (!$this->authorize(AdminAcl::RESOURCE_USERS)) { return $this->null(); @@ -56,11 +56,9 @@ public function includeUser(Subuser $subuser) /** * Return a generic item of server for this subuser. * - * @return \League\Fractal\Resource\Item|\League\Fractal\Resource\NullResource - * * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ - public function includeServer(Subuser $subuser) + public function includeServer(Subuser $subuser): Item|NullResource { if (!$this->authorize(AdminAcl::RESOURCE_SERVERS)) { return $this->null(); diff --git a/app/Transformers/Api/Application/UserTransformer.php b/app/Transformers/Api/Application/UserTransformer.php index 9e2769a13a..14e354b45d 100644 --- a/app/Transformers/Api/Application/UserTransformer.php +++ b/app/Transformers/Api/Application/UserTransformer.php @@ -3,6 +3,8 @@ namespace Pterodactyl\Transformers\Api\Application; use Pterodactyl\Models\User; +use League\Fractal\Resource\Collection; +use League\Fractal\Resource\NullResource; use Pterodactyl\Services\Acl\Api\AdminAcl; class UserTransformer extends BaseTransformer @@ -44,11 +46,9 @@ public function transform(User $user): array /** * Return the servers associated with this user. * - * @return \League\Fractal\Resource\Collection|\League\Fractal\Resource\NullResource - * * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ - public function includeServers(User $user) + public function includeServers(User $user): Collection|NullResource { if (!$this->authorize(AdminAcl::RESOURCE_SERVERS)) { return $this->null(); diff --git a/app/Transformers/Api/Client/AccountTransformer.php b/app/Transformers/Api/Client/AccountTransformer.php index 405a4208a7..1a14555d83 100644 --- a/app/Transformers/Api/Client/AccountTransformer.php +++ b/app/Transformers/Api/Client/AccountTransformer.php @@ -15,11 +15,9 @@ public function getResourceName(): string } /** - * Return basic information about the currently logged in user. - * - * @return array + * Return basic information about the currently logged-in user. */ - public function transform(User $model) + public function transform(User $model): array { return [ 'id' => $model->id, diff --git a/app/Transformers/Api/Client/ActivityLogTransformer.php b/app/Transformers/Api/Client/ActivityLogTransformer.php index 8a79bce820..849fdc865b 100644 --- a/app/Transformers/Api/Client/ActivityLogTransformer.php +++ b/app/Transformers/Api/Client/ActivityLogTransformer.php @@ -30,7 +30,7 @@ public function transform(ActivityLog $model): array 'description' => $model->description, 'properties' => $this->properties($model), 'has_additional_metadata' => $this->hasAdditionalMetadata($model), - 'timestamp' => $model->timestamp->toIso8601String(), + 'timestamp' => $model->timestamp->toAtomString(), ]; } diff --git a/app/Transformers/Api/Client/AllocationTransformer.php b/app/Transformers/Api/Client/AllocationTransformer.php index 0d9bfec7c8..2e63e2bc9c 100644 --- a/app/Transformers/Api/Client/AllocationTransformer.php +++ b/app/Transformers/Api/Client/AllocationTransformer.php @@ -14,12 +14,7 @@ public function getResourceName(): string return 'allocation'; } - /** - * Return basic information about the currently logged in user. - * - * @return array - */ - public function transform(Allocation $model) + public function transform(Allocation $model): array { return [ 'id' => $model->id, diff --git a/app/Transformers/Api/Client/ApiKeyTransformer.php b/app/Transformers/Api/Client/ApiKeyTransformer.php index c7c39c2b45..92ee1a5c66 100644 --- a/app/Transformers/Api/Client/ApiKeyTransformer.php +++ b/app/Transformers/Api/Client/ApiKeyTransformer.php @@ -16,17 +16,15 @@ public function getResourceName(): string /** * Transform this model into a representation that can be consumed by a client. - * - * @return array */ - public function transform(ApiKey $model) + public function transform(ApiKey $model): array { return [ 'identifier' => $model->identifier, 'description' => $model->memo, 'allowed_ips' => $model->allowed_ips, - 'last_used_at' => $model->last_used_at ? $model->last_used_at->toIso8601String() : null, - 'created_at' => $model->created_at->toIso8601String(), + 'last_used_at' => $model->last_used_at ? $model->last_used_at->toAtomString() : null, + 'created_at' => $model->created_at->toAtomString(), ]; } } diff --git a/app/Transformers/Api/Client/BackupTransformer.php b/app/Transformers/Api/Client/BackupTransformer.php index ae6ae52132..298c996427 100644 --- a/app/Transformers/Api/Client/BackupTransformer.php +++ b/app/Transformers/Api/Client/BackupTransformer.php @@ -11,10 +11,7 @@ public function getResourceName(): string return Backup::RESOURCE_NAME; } - /** - * @return array - */ - public function transform(Backup $backup) + public function transform(Backup $backup): array { return [ 'uuid' => $backup->uuid, @@ -24,8 +21,8 @@ public function transform(Backup $backup) 'ignored_files' => $backup->ignored_files, 'checksum' => $backup->checksum, 'bytes' => $backup->bytes, - 'created_at' => $backup->created_at->toIso8601String(), - 'completed_at' => $backup->completed_at ? $backup->completed_at->toIso8601String() : null, + 'created_at' => $backup->created_at->toAtomString(), + 'completed_at' => $backup->completed_at ? $backup->completed_at->toAtomString() : null, ]; } } diff --git a/app/Transformers/Api/Client/DatabaseTransformer.php b/app/Transformers/Api/Client/DatabaseTransformer.php index bdabbeed5f..23e9666377 100644 --- a/app/Transformers/Api/Client/DatabaseTransformer.php +++ b/app/Transformers/Api/Client/DatabaseTransformer.php @@ -3,7 +3,9 @@ namespace Pterodactyl\Transformers\Api\Client; use Pterodactyl\Models\Database; +use League\Fractal\Resource\Item; use Pterodactyl\Models\Permission; +use League\Fractal\Resource\NullResource; use Illuminate\Contracts\Encryption\Encrypter; use Pterodactyl\Contracts\Extensions\HashidsInterface; @@ -11,15 +13,9 @@ class DatabaseTransformer extends BaseClientTransformer { protected array $availableIncludes = ['password']; - /** - * @var \Illuminate\Contracts\Encryption\Encrypter - */ - private $encrypter; + private Encrypter $encrypter; - /** - * @var \Pterodactyl\Contracts\Extensions\HashidsInterface - */ - private $hashids; + private HashidsInterface $hashids; /** * Handle dependency injection. @@ -54,10 +50,8 @@ public function transform(Database $model): array /** * Include the database password in the request. - * - * @return \League\Fractal\Resource\Item|\League\Fractal\Resource\NullResource */ - public function includePassword(Database $database) + public function includePassword(Database $database): Item|NullResource { if (!$this->request->user()->can(Permission::ACTION_DATABASE_VIEW_PASSWORD, $database->server)) { return $this->null(); diff --git a/app/Transformers/Api/Client/EggTransformer.php b/app/Transformers/Api/Client/EggTransformer.php index 25e1350072..8e2e3474e3 100644 --- a/app/Transformers/Api/Client/EggTransformer.php +++ b/app/Transformers/Api/Client/EggTransformer.php @@ -14,10 +14,7 @@ public function getResourceName(): string return Egg::RESOURCE_NAME; } - /** - * @return array - */ - public function transform(Egg $egg) + public function transform(Egg $egg): array { return [ 'uuid' => $egg->uuid, diff --git a/app/Transformers/Api/Client/EggVariableTransformer.php b/app/Transformers/Api/Client/EggVariableTransformer.php index c7725b9783..1294942518 100644 --- a/app/Transformers/Api/Client/EggVariableTransformer.php +++ b/app/Transformers/Api/Client/EggVariableTransformer.php @@ -12,10 +12,7 @@ public function getResourceName(): string return EggVariable::RESOURCE_NAME; } - /** - * @return array - */ - public function transform(EggVariable $variable) + public function transform(EggVariable $variable): array { // This guards against someone incorrectly retrieving variables (haha, me) and then passing // them into the transformer and along to the user. Just throw an exception and break the entire diff --git a/app/Transformers/Api/Client/FileObjectTransformer.php b/app/Transformers/Api/Client/FileObjectTransformer.php index 634a7d46e7..6278dad735 100644 --- a/app/Transformers/Api/Client/FileObjectTransformer.php +++ b/app/Transformers/Api/Client/FileObjectTransformer.php @@ -9,10 +9,8 @@ class FileObjectTransformer extends BaseClientTransformer { /** * Transform a file object response from the daemon into a standardized response. - * - * @return array */ - public function transform(array $item) + public function transform(array $item): array { return [ 'name' => Arr::get($item, 'name'), @@ -22,8 +20,8 @@ public function transform(array $item) 'is_file' => Arr::get($item, 'file', true), 'is_symlink' => Arr::get($item, 'symlink', false), 'mimetype' => Arr::get($item, 'mime', 'application/octet-stream'), - 'created_at' => Carbon::parse(Arr::get($item, 'created', ''))->toIso8601String(), - 'modified_at' => Carbon::parse(Arr::get($item, 'modified', ''))->toIso8601String(), + 'created_at' => Carbon::parse(Arr::get($item, 'created', ''))->toAtomString(), + 'modified_at' => Carbon::parse(Arr::get($item, 'modified', ''))->toAtomString(), ]; } diff --git a/app/Transformers/Api/Client/ScheduleTransformer.php b/app/Transformers/Api/Client/ScheduleTransformer.php index 07dbf77cbc..98c783f45a 100644 --- a/app/Transformers/Api/Client/ScheduleTransformer.php +++ b/app/Transformers/Api/Client/ScheduleTransformer.php @@ -4,7 +4,7 @@ use Pterodactyl\Models\Task; use Pterodactyl\Models\Schedule; -use Illuminate\Database\Eloquent\Model; +use League\Fractal\Resource\Collection; class ScheduleTransformer extends BaseClientTransformer { @@ -22,10 +22,8 @@ public function getResourceName(): string /** * Returns a transformed schedule model such that a client can view the information. - * - * @return array */ - public function transform(Schedule $model) + public function transform(Schedule $model): array { return [ 'id' => $model->id, @@ -40,21 +38,19 @@ public function transform(Schedule $model) '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(), - 'updated_at' => $model->updated_at->toIso8601String(), + 'last_run_at' => $model->last_run_at?->toAtomString(), + 'next_run_at' => $model->next_run_at?->toAtomString(), + 'created_at' => $model->created_at->toAtomString(), + 'updated_at' => $model->updated_at->toAtomString(), ]; } /** * Allows attaching the tasks specific to the schedule in the response. * - * @return \League\Fractal\Resource\Collection - * * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ - public function includeTasks(Schedule $model) + public function includeTasks(Schedule $model): Collection { return $this->collection( $model->tasks, diff --git a/app/Transformers/Api/Client/ServerTransformer.php b/app/Transformers/Api/Client/ServerTransformer.php index 53f4adf4ba..8ae4ed4a6a 100644 --- a/app/Transformers/Api/Client/ServerTransformer.php +++ b/app/Transformers/Api/Client/ServerTransformer.php @@ -5,17 +5,17 @@ use Pterodactyl\Models\Egg; use Pterodactyl\Models\Server; use Pterodactyl\Models\Subuser; +use League\Fractal\Resource\Item; use Pterodactyl\Models\Allocation; use Pterodactyl\Models\Permission; use Illuminate\Container\Container; use Pterodactyl\Models\EggVariable; +use League\Fractal\Resource\Collection; +use League\Fractal\Resource\NullResource; use Pterodactyl\Services\Servers\StartupCommandService; class ServerTransformer extends BaseClientTransformer { - /** - * @var string[] - */ protected array $defaultIncludes = ['allocations', 'variables']; protected array $availableIncludes = ['egg', 'subusers']; @@ -77,11 +77,9 @@ public function transform(Server $server): array /** * Returns the allocations associated with this server. * - * @return \League\Fractal\Resource\Collection - * * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ - public function includeAllocations(Server $server) + public function includeAllocations(Server $server): Collection { $transformer = $this->makeTransformer(AllocationTransformer::class); @@ -104,11 +102,9 @@ public function includeAllocations(Server $server) } /** - * @return \League\Fractal\Resource\Collection|\League\Fractal\Resource\NullResource - * * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ - public function includeVariables(Server $server) + public function includeVariables(Server $server): Collection|NullResource { if (!$this->request->user()->can(Permission::ACTION_STARTUP_READ, $server)) { return $this->null(); @@ -124,11 +120,9 @@ public function includeVariables(Server $server) /** * Returns the egg associated with this server. * - * @return \League\Fractal\Resource\Item - * * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ - public function includeEgg(Server $server) + public function includeEgg(Server $server): Item { return $this->item($server->egg, $this->makeTransformer(EggTransformer::class), Egg::RESOURCE_NAME); } @@ -136,11 +130,9 @@ public function includeEgg(Server $server) /** * Returns the subusers associated with this server. * - * @return \League\Fractal\Resource\Collection|\League\Fractal\Resource\NullResource - * * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ - public function includeSubusers(Server $server) + public function includeSubusers(Server $server): Collection|NullResource { if (!$this->request->user()->can(Permission::ACTION_USER_READ, $server)) { return $this->null(); diff --git a/app/Transformers/Api/Client/StatsTransformer.php b/app/Transformers/Api/Client/StatsTransformer.php index 66827bf363..6b323b3154 100644 --- a/app/Transformers/Api/Client/StatsTransformer.php +++ b/app/Transformers/Api/Client/StatsTransformer.php @@ -14,10 +14,8 @@ public function getResourceName(): string /** * Transform stats from the daemon into a result set that can be used in * the client API. - * - * @return array */ - public function transform(array $data) + public function transform(array $data): array { return [ 'current_state' => Arr::get($data, 'state', 'stopped'), diff --git a/app/Transformers/Api/Client/SubuserTransformer.php b/app/Transformers/Api/Client/SubuserTransformer.php index 7a8bfde0ed..2902d1f7a2 100644 --- a/app/Transformers/Api/Client/SubuserTransformer.php +++ b/app/Transformers/Api/Client/SubuserTransformer.php @@ -17,11 +17,9 @@ public function getResourceName(): string /** * Transforms a subuser into a model that can be shown to a front-end user. * - * @return array|void - * * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ - public function transform(Subuser $model) + public function transform(Subuser $model): array { return array_merge( $this->makeTransformer(UserTransformer::class)->transform($model->user), diff --git a/app/Transformers/Api/Client/TaskTransformer.php b/app/Transformers/Api/Client/TaskTransformer.php index a2e62cf513..52f0e2b5d1 100644 --- a/app/Transformers/Api/Client/TaskTransformer.php +++ b/app/Transformers/Api/Client/TaskTransformer.php @@ -16,10 +16,8 @@ public function getResourceName(): string /** * Transforms a schedule's task into a client viewable format. - * - * @return array */ - public function transform(Task $model) + public function transform(Task $model): array { return [ 'id' => $model->id, @@ -29,8 +27,8 @@ public function transform(Task $model) '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(), + 'created_at' => $model->created_at->toAtomString(), + 'updated_at' => $model->updated_at->toAtomString(), ]; } } diff --git a/app/Transformers/Api/Client/UserSSHKeyTransformer.php b/app/Transformers/Api/Client/UserSSHKeyTransformer.php index 1a9349e67c..015a017b69 100644 --- a/app/Transformers/Api/Client/UserSSHKeyTransformer.php +++ b/app/Transformers/Api/Client/UserSSHKeyTransformer.php @@ -20,7 +20,7 @@ public function transform(UserSSHKey $model): array 'name' => $model->name, 'fingerprint' => $model->fingerprint, 'public_key' => $model->public_key, - 'created_at' => $model->created_at->toIso8601String(), + 'created_at' => $model->created_at->toAtomString(), ]; } } diff --git a/app/Transformers/Api/Client/UserTransformer.php b/app/Transformers/Api/Client/UserTransformer.php index 468232f8d6..04bc70ea6d 100644 --- a/app/Transformers/Api/Client/UserTransformer.php +++ b/app/Transformers/Api/Client/UserTransformer.php @@ -18,10 +18,8 @@ public function getResourceName(): string /** * Transforms a User model into a representation that can be shown to regular * users of the API. - * - * @return array */ - public function transform(User $model) + public function transform(User $model): array { return [ 'uuid' => $model->uuid, @@ -29,7 +27,7 @@ public function transform(User $model) 'email' => $model->email, 'image' => 'https://gravatar.com/avatar/' . md5(Str::lower($model->email)), '2fa_enabled' => $model->use_totp, - 'created_at' => $model->created_at->toIso8601String(), + 'created_at' => $model->created_at->toAtomString(), ]; } } diff --git a/app/helpers.php b/app/helpers.php index 9c1b4ed1b8..59aa7166ad 100644 --- a/app/helpers.php +++ b/app/helpers.php @@ -4,14 +4,10 @@ /** * Deal with normal (and irritating) PHP behavior to determine if * a value is a non-float positive integer. - * - * @param mixed $value - * - * @return bool */ - function is_digit($value) + function is_digit(mixed $value): bool { - return is_bool($value) ? false : ctype_digit(strval($value)); + return !is_bool($value) && ctype_digit(strval($value)); } } @@ -19,14 +15,8 @@ function is_digit($value) /** * Get an object using dot notation. An object key with a value of null is still considered valid * and will not trigger the response of a default value (unlike object_get). - * - * @param object $object - * @param string $key - * @param null $default - * - * @return mixed */ - function object_get_strict($object, $key, $default = null) + function object_get_strict(object $object, ?string $key, $default = null): mixed { if (is_null($key) || trim($key) == '') { return $object; diff --git a/artisan b/artisan index 5c23e2e24f..240ab7a91d 100755 --- a/artisan +++ b/artisan @@ -11,13 +11,13 @@ define('LARAVEL_START', microtime(true)); | Composer provides a convenient, automatically generated class loader | for our application. We just need to utilize it! We'll require it | into the script here so that we do not have to worry about the -| loading of any our classes "manually". Feels great to relax. +| loading of our classes manually. It's great to relax. | */ -require __DIR__.'/vendor/autoload.php'; +require __DIR__ . '/vendor/autoload.php'; -$app = require_once __DIR__.'/bootstrap/app.php'; +$app = require_once __DIR__ . '/bootstrap/app.php'; /* |-------------------------------------------------------------------------- @@ -33,8 +33,8 @@ $app = require_once __DIR__.'/bootstrap/app.php'; $kernel = $app->make(Illuminate\Contracts\Console\Kernel::class); $status = $kernel->handle( - $input = new Symfony\Component\Console\Input\ArgvInput, - new Symfony\Component\Console\Output\ConsoleOutput + $input = new Symfony\Component\Console\Input\ArgvInput(), + new Symfony\Component\Console\Output\ConsoleOutput() ); /* diff --git a/composer.json b/composer.json index 985f64f3a6..2d40267819 100644 --- a/composer.json +++ b/composer.json @@ -3,57 +3,66 @@ "description": "The free, open-source game management panel. Supporting Minecraft, Spigot, BungeeCord, and SRCDS servers.", "license": "MIT", "authors": [ + { + "name": "Matthew Penner", + "email": "matthew@pterodactyl.io", + "homepage": "https://github.com/matthewpi", + "role": "Lead Developer" + }, { "name": "Dane Everitt", "email": "dane@daneeveritt.com", "homepage": "https://github.com/DaneEveritt", - "role": "Lead Developer" + "role": "Developer" } ], "require": { - "php": "^7.4 || ^8.0 || ^8.1", + "php": "^8.0.2 || ^8.1", "ext-json": "*", "ext-mbstring": "*", "ext-pdo": "*", "ext-pdo_mysql": "*", + "ext-posix": "*", "ext-zip": "*", - "aws/aws-sdk-php": "^3.171", - "doctrine/dbal": "~2.13.9", - "fruitcake/laravel-cors": "~3.0.0", - "guzzlehttp/guzzle": "~7.4.4", + "aws/aws-sdk-php": "~3.238.2", + "doctrine/dbal": "~3.4.5", + "guzzlehttp/guzzle": "~7.5.0", "hashids/hashids": "~4.1.0", "laracasts/utilities": "~3.2.1", - "laravel/framework": "^8.83", + "laravel/framework": "^9.34.0", "laravel/helpers": "~1.5.0", "laravel/sanctum": "~2.15.1", "laravel/tinker": "~2.7.2", - "laravel/ui": "~3.4.5", - "lcobucci/jwt": "~4.1.5", - "league/flysystem-aws-s3-v3": "~1.0.29", - "league/flysystem-memory": "~1.0.2", - "matriphe/iso-639": "~1.2.0", + "laravel/ui": "~3.4.6", + "lcobucci/jwt": "~4.2.1", + "league/flysystem-aws-s3-v3": "~3.5.0", + "league/flysystem-memory": "~3.3.0", + "matriphe/iso-639": "~1.2", "phpseclib/phpseclib": "~3.0", "pragmarx/google2fa": "~5.0.0", - "predis/predis": "~1.1.10", - "prologue/alerts": "~0.4.8", - "psr/cache": "~1.0.1", + "predis/predis": "~2.0.2", + "prologue/alerts": "~1.0.0", + "psr/cache": "~3.0.0", "s1lentium/iptools": "~1.1.1", - "spatie/laravel-fractal": "~5.8.1", - "spatie/laravel-query-builder": "~3.6.2", - "staudenmeir/belongs-to-through": "~2.11.2", - "symfony/yaml": "~4.4.37", - "webmozart/assert": "~1.10.0" + "spatie/laravel-fractal": "~6.0.2", + "spatie/laravel-query-builder": "~5.0.3", + "staudenmeir/belongs-to-through": "~2.12.1", + "symfony/http-client": "~6.0", + "symfony/mailgun-mailer": "~6.0", + "symfony/postmark-mailer": "~6.0", + "symfony/yaml": "~5.4", + "webmozart/assert": "~1.11" }, "require-dev": { - "barryvdh/laravel-ide-helper": "^2.12", - "facade/ignition": "^2.17", - "fakerphp/faker": "^1.19", - "friendsofphp/php-cs-fixer": "^3.8", - "itsgoingd/clockwork": "^5.1", - "mockery/mockery": "^1.5", - "nunomaduro/collision": "^5.11", - "php-mock/php-mock-phpunit": "^2.6", - "phpunit/phpunit": "^9.5" + "barryvdh/laravel-ide-helper": "~2.12.3", + "fakerphp/faker": "~1.20", + "friendsofphp/php-cs-fixer": "~3.11", + "itsgoingd/clockwork": "~5.1", + "mockery/mockery": "~1.5", + "nunomaduro/collision": "~6.3", + "php-mock/php-mock-phpunit": "~2.6", + "phpunit/phpunit": "~9.5", + "spatie/laravel-ignition": "~1.5" }, "autoload": { "files": [ @@ -86,11 +95,11 @@ }, "prefer-stable": true, "config": { - "platform": { - "php": "7.4.0" - }, + "optimize-autoloader": true, "preferred-install": "dist", "sort-packages": true, - "optimize-autoloader": false + "platform": { + "php": "8.0.2" + } } } diff --git a/composer.lock b/composer.lock index b293911e76..3b04605863 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "29f7542c6f23a5941c840e1335923cc2", + "content-hash": "8244c912d7f8c69a2a29aa1ee45982cb", "packages": [ { "name": "aws/aws-crt-php", @@ -58,16 +58,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.224.0", + "version": "3.238.2", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "bc5eb18414ef703c5f39a5a009a437c74c228306" + "reference": "f2a1351b5eb5fe17508cc5c907a786955882b93c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/bc5eb18414ef703c5f39a5a009a437c74c228306", - "reference": "bc5eb18414ef703c5f39a5a009a437c74c228306", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/f2a1351b5eb5fe17508cc5c907a786955882b93c", + "reference": "f2a1351b5eb5fe17508cc5c907a786955882b93c", "shasum": "" }, "require": { @@ -75,9 +75,9 @@ "ext-json": "*", "ext-pcre": "*", "ext-simplexml": "*", - "guzzlehttp/guzzle": "^5.3.3 || ^6.2.1 || ^7.0", + "guzzlehttp/guzzle": "^6.5.8 || ^7.4.5", "guzzlehttp/promises": "^1.4.0", - "guzzlehttp/psr7": "^1.7.0 || ^2.1.1", + "guzzlehttp/psr7": "^1.8.5 || ^2.3", "mtdowling/jmespath.php": "^2.6", "php": ">=5.5" }, @@ -85,6 +85,8 @@ "andrewsville/php-token-reflection": "^1.4", "aws/aws-php-sns-message-validator": "~1.0", "behat/behat": "~3.0", + "composer/composer": "^1.10.22", + "dms/phpunit-arraysubset-asserts": "^0.4.0", "doctrine/cache": "~1.4", "ext-dom": "*", "ext-openssl": "*", @@ -92,10 +94,11 @@ "ext-sockets": "*", "nette/neon": "^2.3", "paragonie/random_compat": ">= 2", - "phpunit/phpunit": "^4.8.35 || ^5.6.3", + "phpunit/phpunit": "^4.8.35 || ^5.6.3 || ^9.5", "psr/cache": "^1.0", "psr/simple-cache": "^1.0", - "sebastian/comparator": "^1.2.3" + "sebastian/comparator": "^1.2.3 || ^4.0", + "yoast/phpunit-polyfills": "^1.0" }, "suggest": { "aws/aws-php-sns-message-validator": "To validate incoming SNS notifications", @@ -143,32 +146,32 @@ "support": { "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.224.0" + "source": "https://github.com/aws/aws-sdk-php/tree/3.238.2" }, - "time": "2022-05-27T20:23:28+00:00" + "time": "2022-10-06T18:29:40+00:00" }, { "name": "brick/math", - "version": "0.9.3", + "version": "0.10.2", "source": { "type": "git", "url": "https://github.com/brick/math.git", - "reference": "ca57d18f028f84f777b2168cd1911b0dee2343ae" + "reference": "459f2781e1a08d52ee56b0b1444086e038561e3f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/brick/math/zipball/ca57d18f028f84f777b2168cd1911b0dee2343ae", - "reference": "ca57d18f028f84f777b2168cd1911b0dee2343ae", + "url": "https://api.github.com/repos/brick/math/zipball/459f2781e1a08d52ee56b0b1444086e038561e3f", + "reference": "459f2781e1a08d52ee56b0b1444086e038561e3f", "shasum": "" }, "require": { "ext-json": "*", - "php": "^7.1 || ^8.0" + "php": "^7.4 || ^8.0" }, "require-dev": { "php-coveralls/php-coveralls": "^2.2", - "phpunit/phpunit": "^7.5.15 || ^8.5 || ^9.0", - "vimeo/psalm": "4.9.2" + "phpunit/phpunit": "^9.0", + "vimeo/psalm": "4.25.0" }, "type": "library", "autoload": { @@ -193,19 +196,15 @@ ], "support": { "issues": "https://github.com/brick/math/issues", - "source": "https://github.com/brick/math/tree/0.9.3" + "source": "https://github.com/brick/math/tree/0.10.2" }, "funding": [ { "url": "https://github.com/BenMorel", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/brick/math", - "type": "tidelift" } ], - "time": "2021-08-15T20:50:18+00:00" + "time": "2022-08-10T22:54:19+00:00" }, { "name": "dflydev/dot-access-data", @@ -377,35 +376,38 @@ }, { "name": "doctrine/dbal", - "version": "2.13.9", + "version": "3.4.5", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "c480849ca3ad6706a39c970cdfe6888fa8a058b8" + "reference": "a5a58773109c0abb13e658c8ccd92aeec8d07f9e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/c480849ca3ad6706a39c970cdfe6888fa8a058b8", - "reference": "c480849ca3ad6706a39c970cdfe6888fa8a058b8", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/a5a58773109c0abb13e658c8ccd92aeec8d07f9e", + "reference": "a5a58773109c0abb13e658c8ccd92aeec8d07f9e", "shasum": "" }, "require": { - "doctrine/cache": "^1.0|^2.0", + "composer-runtime-api": "^2", + "doctrine/cache": "^1.11|^2.0", "doctrine/deprecations": "^0.5.3|^1", "doctrine/event-manager": "^1.0", - "ext-pdo": "*", - "php": "^7.1 || ^8" + "php": "^7.4 || ^8.0", + "psr/cache": "^1|^2|^3", + "psr/log": "^1|^2|^3" }, "require-dev": { - "doctrine/coding-standard": "9.0.0", - "jetbrains/phpstorm-stubs": "2021.1", - "phpstan/phpstan": "1.4.6", - "phpunit/phpunit": "^7.5.20|^8.5|9.5.16", - "psalm/plugin-phpunit": "0.16.1", - "squizlabs/php_codesniffer": "3.6.2", - "symfony/cache": "^4.4", - "symfony/console": "^2.0.5|^3.0|^4.0|^5.0", - "vimeo/psalm": "4.22.0" + "doctrine/coding-standard": "10.0.0", + "jetbrains/phpstorm-stubs": "2022.2", + "phpstan/phpstan": "1.8.3", + "phpstan/phpstan-strict-rules": "^1.3", + "phpunit/phpunit": "9.5.24", + "psalm/plugin-phpunit": "0.17.0", + "squizlabs/php_codesniffer": "3.7.1", + "symfony/cache": "^5.4|^6.0", + "symfony/console": "^4.4|^5.4|^6.0", + "vimeo/psalm": "4.27.0" }, "suggest": { "symfony/console": "For helpful console commands such as SQL execution and import of files." @@ -416,7 +418,7 @@ "type": "library", "autoload": { "psr-4": { - "Doctrine\\DBAL\\": "lib/Doctrine/DBAL" + "Doctrine\\DBAL\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -459,14 +461,13 @@ "queryobject", "sasql", "sql", - "sqlanywhere", "sqlite", "sqlserver", "sqlsrv" ], "support": { "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/2.13.9" + "source": "https://github.com/doctrine/dbal/tree/3.4.5" }, "funding": [ { @@ -482,7 +483,7 @@ "type": "tidelift" } ], - "time": "2022-05-02T20:28:55+00:00" + "time": "2022-09-23T17:48:57+00:00" }, { "name": "doctrine/deprecations", @@ -529,34 +530,31 @@ }, { "name": "doctrine/event-manager", - "version": "1.1.1", + "version": "1.1.2", "source": { "type": "git", "url": "https://github.com/doctrine/event-manager.git", - "reference": "41370af6a30faa9dc0368c4a6814d596e81aba7f" + "reference": "eb2ecf80e3093e8f3c2769ac838e27d8ede8e683" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/event-manager/zipball/41370af6a30faa9dc0368c4a6814d596e81aba7f", - "reference": "41370af6a30faa9dc0368c4a6814d596e81aba7f", + "url": "https://api.github.com/repos/doctrine/event-manager/zipball/eb2ecf80e3093e8f3c2769ac838e27d8ede8e683", + "reference": "eb2ecf80e3093e8f3c2769ac838e27d8ede8e683", "shasum": "" }, "require": { "php": "^7.1 || ^8.0" }, "conflict": { - "doctrine/common": "<2.9@dev" + "doctrine/common": "<2.9" }, "require-dev": { - "doctrine/coding-standard": "^6.0", - "phpunit/phpunit": "^7.0" + "doctrine/coding-standard": "^9", + "phpstan/phpstan": "~1.4.10 || ^1.5.4", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "vimeo/psalm": "^4.22" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, "autoload": { "psr-4": { "Doctrine\\Common\\": "lib/Doctrine/Common" @@ -603,7 +601,7 @@ ], "support": { "issues": "https://github.com/doctrine/event-manager/issues", - "source": "https://github.com/doctrine/event-manager/tree/1.1.x" + "source": "https://github.com/doctrine/event-manager/tree/1.1.2" }, "funding": [ { @@ -619,32 +617,32 @@ "type": "tidelift" } ], - "time": "2020-05-29T18:28:51+00:00" + "time": "2022-07-27T22:18:11+00:00" }, { "name": "doctrine/inflector", - "version": "2.0.4", + "version": "2.0.5", "source": { "type": "git", "url": "https://github.com/doctrine/inflector.git", - "reference": "8b7ff3e4b7de6b2c84da85637b59fd2880ecaa89" + "reference": "ade2b3bbfb776f27f0558e26eed43b5d9fe1b392" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/inflector/zipball/8b7ff3e4b7de6b2c84da85637b59fd2880ecaa89", - "reference": "8b7ff3e4b7de6b2c84da85637b59fd2880ecaa89", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/ade2b3bbfb776f27f0558e26eed43b5d9fe1b392", + "reference": "ade2b3bbfb776f27f0558e26eed43b5d9fe1b392", "shasum": "" }, "require": { "php": "^7.2 || ^8.0" }, "require-dev": { - "doctrine/coding-standard": "^8.2", - "phpstan/phpstan": "^0.12", - "phpstan/phpstan-phpunit": "^0.12", - "phpstan/phpstan-strict-rules": "^0.12", - "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", - "vimeo/psalm": "^4.10" + "doctrine/coding-standard": "^9", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-phpunit": "^1.1", + "phpstan/phpstan-strict-rules": "^1.3", + "phpunit/phpunit": "^8.5 || ^9.5", + "vimeo/psalm": "^4.25" }, "type": "library", "autoload": { @@ -694,7 +692,7 @@ ], "support": { "issues": "https://github.com/doctrine/inflector/issues", - "source": "https://github.com/doctrine/inflector/tree/2.0.4" + "source": "https://github.com/doctrine/inflector/tree/2.0.5" }, "funding": [ { @@ -710,7 +708,7 @@ "type": "tidelift" } ], - "time": "2021-10-22T20:16:43+00:00" + "time": "2022-09-07T09:01:28+00:00" }, { "name": "doctrine/lexer", @@ -790,16 +788,16 @@ }, { "name": "dragonmantank/cron-expression", - "version": "v3.3.1", + "version": "v3.3.2", "source": { "type": "git", "url": "https://github.com/dragonmantank/cron-expression.git", - "reference": "be85b3f05b46c39bbc0d95f6c071ddff669510fa" + "reference": "782ca5968ab8b954773518e9e49a6f892a34b2a8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/be85b3f05b46c39bbc0d95f6c071ddff669510fa", - "reference": "be85b3f05b46c39bbc0d95f6c071ddff669510fa", + "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/782ca5968ab8b954773518e9e49a6f892a34b2a8", + "reference": "782ca5968ab8b954773518e9e49a6f892a34b2a8", "shasum": "" }, "require": { @@ -839,7 +837,7 @@ ], "support": { "issues": "https://github.com/dragonmantank/cron-expression/issues", - "source": "https://github.com/dragonmantank/cron-expression/tree/v3.3.1" + "source": "https://github.com/dragonmantank/cron-expression/tree/v3.3.2" }, "funding": [ { @@ -847,31 +845,31 @@ "type": "github" } ], - "time": "2022-01-18T15:43:28+00:00" + "time": "2022-09-10T18:51:20+00:00" }, { "name": "egulias/email-validator", - "version": "2.1.25", + "version": "3.2.1", "source": { "type": "git", "url": "https://github.com/egulias/EmailValidator.git", - "reference": "0dbf5d78455d4d6a41d186da50adc1122ec066f4" + "reference": "f88dcf4b14af14a98ad96b14b2b317969eab6715" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/0dbf5d78455d4d6a41d186da50adc1122ec066f4", - "reference": "0dbf5d78455d4d6a41d186da50adc1122ec066f4", + "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/f88dcf4b14af14a98ad96b14b2b317969eab6715", + "reference": "f88dcf4b14af14a98ad96b14b2b317969eab6715", "shasum": "" }, "require": { - "doctrine/lexer": "^1.0.1", - "php": ">=5.5", - "symfony/polyfill-intl-idn": "^1.10" + "doctrine/lexer": "^1.2", + "php": ">=7.2", + "symfony/polyfill-intl-idn": "^1.15" }, "require-dev": { - "dominicsayers/isemail": "^3.0.7", - "phpunit/phpunit": "^4.8.36|^7.5.15", - "satooshi/php-coveralls": "^1.0.1" + "php-coveralls/php-coveralls": "^2.2", + "phpunit/phpunit": "^8.5.8|^9.3.3", + "vimeo/psalm": "^4" }, "suggest": { "ext-intl": "PHP Internationalization Libraries are required to use the SpoofChecking validation" @@ -879,7 +877,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.1.x-dev" + "dev-master": "3.0.x-dev" } }, "autoload": { @@ -907,7 +905,7 @@ ], "support": { "issues": "https://github.com/egulias/EmailValidator/issues", - "source": "https://github.com/egulias/EmailValidator/tree/2.1.25" + "source": "https://github.com/egulias/EmailValidator/tree/3.2.1" }, "funding": [ { @@ -915,86 +913,7 @@ "type": "github" } ], - "time": "2020-12-29T14:50:06+00:00" - }, - { - "name": "fruitcake/laravel-cors", - "version": "v3.0.0", - "source": { - "type": "git", - "url": "https://github.com/fruitcake/laravel-cors.git", - "reference": "7c036ec08972d8d5d9db637e772af6887828faf5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/fruitcake/laravel-cors/zipball/7c036ec08972d8d5d9db637e772af6887828faf5", - "reference": "7c036ec08972d8d5d9db637e772af6887828faf5", - "shasum": "" - }, - "require": { - "fruitcake/php-cors": "^1.2", - "illuminate/contracts": "^6|^7|^8|^9", - "illuminate/support": "^6|^7|^8|^9", - "php": "^7.4|^8.0" - }, - "require-dev": { - "laravel/framework": "^6|^7.24|^8", - "orchestra/testbench-dusk": "^4|^5|^6|^7", - "phpunit/phpunit": "^9", - "squizlabs/php_codesniffer": "^3.5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0-dev" - }, - "laravel": { - "providers": [ - "Fruitcake\\Cors\\CorsServiceProvider" - ] - } - }, - "autoload": { - "psr-4": { - "Fruitcake\\Cors\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fruitcake", - "homepage": "https://fruitcake.nl" - }, - { - "name": "Barry vd. Heuvel", - "email": "barryvdh@gmail.com" - } - ], - "description": "Adds CORS (Cross-Origin Resource Sharing) headers support in your Laravel application", - "keywords": [ - "api", - "cors", - "crossdomain", - "laravel" - ], - "support": { - "issues": "https://github.com/fruitcake/laravel-cors/issues", - "source": "https://github.com/fruitcake/laravel-cors/tree/v3.0.0" - }, - "funding": [ - { - "url": "https://fruitcake.nl", - "type": "custom" - }, - { - "url": "https://github.com/barryvdh", - "type": "github" - } - ], - "time": "2022-02-23T14:53:22+00:00" + "time": "2022-06-18T20:57:19+00:00" }, { "name": "fruitcake/php-cors", @@ -1069,24 +988,24 @@ }, { "name": "graham-campbell/result-type", - "version": "v1.0.4", + "version": "v1.1.0", "source": { "type": "git", "url": "https://github.com/GrahamCampbell/Result-Type.git", - "reference": "0690bde05318336c7221785f2a932467f98b64ca" + "reference": "a878d45c1914464426dc94da61c9e1d36ae262a8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/0690bde05318336c7221785f2a932467f98b64ca", - "reference": "0690bde05318336c7221785f2a932467f98b64ca", + "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/a878d45c1914464426dc94da61c9e1d36ae262a8", + "reference": "a878d45c1914464426dc94da61c9e1d36ae262a8", "shasum": "" }, "require": { - "php": "^7.0 || ^8.0", - "phpoption/phpoption": "^1.8" + "php": "^7.2.5 || ^8.0", + "phpoption/phpoption": "^1.9" }, "require-dev": { - "phpunit/phpunit": "^6.5.14 || ^7.5.20 || ^8.5.19 || ^9.5.8" + "phpunit/phpunit": "^8.5.28 || ^9.5.21" }, "type": "library", "autoload": { @@ -1115,7 +1034,7 @@ ], "support": { "issues": "https://github.com/GrahamCampbell/Result-Type/issues", - "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.0.4" + "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.0" }, "funding": [ { @@ -1127,26 +1046,26 @@ "type": "tidelift" } ], - "time": "2021-11-21T21:41:47+00:00" + "time": "2022-07-30T15:56:11+00:00" }, { "name": "guzzlehttp/guzzle", - "version": "7.4.4", + "version": "7.5.0", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "e3ff079b22820c2029d4c2a87796b6a0b8716ad8" + "reference": "b50a2a1251152e43f6a37f0fa053e730a67d25ba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/e3ff079b22820c2029d4c2a87796b6a0b8716ad8", - "reference": "e3ff079b22820c2029d4c2a87796b6a0b8716ad8", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/b50a2a1251152e43f6a37f0fa053e730a67d25ba", + "reference": "b50a2a1251152e43f6a37f0fa053e730a67d25ba", "shasum": "" }, "require": { "ext-json": "*", "guzzlehttp/promises": "^1.5", - "guzzlehttp/psr7": "^1.8.3 || ^2.1", + "guzzlehttp/psr7": "^1.9 || ^2.4", "php": "^7.2.5 || ^8.0", "psr/http-client": "^1.0", "symfony/deprecation-contracts": "^2.2 || ^3.0" @@ -1155,10 +1074,10 @@ "psr/http-client-implementation": "1.0" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.4.1", + "bamarni/composer-bin-plugin": "^1.8.1", "ext-curl": "*", "php-http/client-integration-tests": "^3.0", - "phpunit/phpunit": "^8.5.5 || ^9.3.5", + "phpunit/phpunit": "^8.5.29 || ^9.5.23", "psr/log": "^1.1 || ^2.0 || ^3.0" }, "suggest": { @@ -1168,8 +1087,12 @@ }, "type": "library", "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + }, "branch-alias": { - "dev-master": "7.4-dev" + "dev-master": "7.5-dev" } }, "autoload": { @@ -1235,7 +1158,7 @@ ], "support": { "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.4.4" + "source": "https://github.com/guzzle/guzzle/tree/7.5.0" }, "funding": [ { @@ -1251,20 +1174,20 @@ "type": "tidelift" } ], - "time": "2022-06-09T21:39:15+00:00" + "time": "2022-08-28T15:39:27+00:00" }, { "name": "guzzlehttp/promises", - "version": "1.5.1", + "version": "1.5.2", "source": { "type": "git", "url": "https://github.com/guzzle/promises.git", - "reference": "fe752aedc9fd8fcca3fe7ad05d419d32998a06da" + "reference": "b94b2807d85443f9719887892882d0329d1e2598" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/fe752aedc9fd8fcca3fe7ad05d419d32998a06da", - "reference": "fe752aedc9fd8fcca3fe7ad05d419d32998a06da", + "url": "https://api.github.com/repos/guzzle/promises/zipball/b94b2807d85443f9719887892882d0329d1e2598", + "reference": "b94b2807d85443f9719887892882d0329d1e2598", "shasum": "" }, "require": { @@ -1319,7 +1242,7 @@ ], "support": { "issues": "https://github.com/guzzle/promises/issues", - "source": "https://github.com/guzzle/promises/tree/1.5.1" + "source": "https://github.com/guzzle/promises/tree/1.5.2" }, "funding": [ { @@ -1335,20 +1258,20 @@ "type": "tidelift" } ], - "time": "2021-10-22T20:56:57+00:00" + "time": "2022-08-28T14:55:35+00:00" }, { "name": "guzzlehttp/psr7", - "version": "2.2.1", + "version": "2.4.1", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "c94a94f120803a18554c1805ef2e539f8285f9a2" + "reference": "69568e4293f4fa993f3b0e51c9723e1e17c41379" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/c94a94f120803a18554c1805ef2e539f8285f9a2", - "reference": "c94a94f120803a18554c1805ef2e539f8285f9a2", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/69568e4293f4fa993f3b0e51c9723e1e17c41379", + "reference": "69568e4293f4fa993f3b0e51c9723e1e17c41379", "shasum": "" }, "require": { @@ -1362,17 +1285,21 @@ "psr/http-message-implementation": "1.0" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.4.1", + "bamarni/composer-bin-plugin": "^1.8.1", "http-interop/http-factory-tests": "^0.9", - "phpunit/phpunit": "^8.5.8 || ^9.3.10" + "phpunit/phpunit": "^8.5.29 || ^9.5.23" }, "suggest": { "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" }, "type": "library", "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + }, "branch-alias": { - "dev-master": "2.2-dev" + "dev-master": "2.4-dev" } }, "autoload": { @@ -1434,7 +1361,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.2.1" + "source": "https://github.com/guzzle/psr7/tree/2.4.1" }, "funding": [ { @@ -1450,7 +1377,7 @@ "type": "tidelift" } ], - "time": "2022-03-20T21:55:58+00:00" + "time": "2022-08-28T14:45:39+00:00" }, { "name": "hashids/hashids", @@ -1585,56 +1512,57 @@ }, { "name": "laravel/framework", - "version": "v8.83.14", + "version": "v9.34.0", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "141cf126f1746c7264f59aa78c923a84eaab501e" + "reference": "b7af7ff35497eb1c4e61652f522a862164dbba5a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/141cf126f1746c7264f59aa78c923a84eaab501e", - "reference": "141cf126f1746c7264f59aa78c923a84eaab501e", + "url": "https://api.github.com/repos/laravel/framework/zipball/b7af7ff35497eb1c4e61652f522a862164dbba5a", + "reference": "b7af7ff35497eb1c4e61652f522a862164dbba5a", "shasum": "" }, "require": { - "doctrine/inflector": "^1.4|^2.0", - "dragonmantank/cron-expression": "^3.0.2", - "egulias/email-validator": "^2.1.10", - "ext-json": "*", + "doctrine/inflector": "^2.0", + "dragonmantank/cron-expression": "^3.3.2", + "egulias/email-validator": "^3.2.1", "ext-mbstring": "*", "ext-openssl": "*", - "laravel/serializable-closure": "^1.0", - "league/commonmark": "^1.3|^2.0.2", - "league/flysystem": "^1.1", + "fruitcake/php-cors": "^1.2", + "laravel/serializable-closure": "^1.2.2", + "league/commonmark": "^2.2", + "league/flysystem": "^3.0.16", "monolog/monolog": "^2.0", - "nesbot/carbon": "^2.53.1", - "opis/closure": "^3.6", - "php": "^7.3|^8.0", - "psr/container": "^1.0", - "psr/log": "^1.0|^2.0", - "psr/simple-cache": "^1.0", + "nesbot/carbon": "^2.62.1", + "nunomaduro/termwind": "^1.13", + "php": "^8.0.2", + "psr/container": "^1.1.1|^2.0.1", + "psr/log": "^1.0|^2.0|^3.0", + "psr/simple-cache": "^1.0|^2.0|^3.0", "ramsey/uuid": "^4.2.2", - "swiftmailer/swiftmailer": "^6.3", - "symfony/console": "^5.4", - "symfony/error-handler": "^5.4", - "symfony/finder": "^5.4", - "symfony/http-foundation": "^5.4", - "symfony/http-kernel": "^5.4", - "symfony/mime": "^5.4", - "symfony/process": "^5.4", - "symfony/routing": "^5.4", - "symfony/var-dumper": "^5.4", - "tijsverkoyen/css-to-inline-styles": "^2.2.2", + "symfony/console": "^6.0.9", + "symfony/error-handler": "^6.0", + "symfony/finder": "^6.0", + "symfony/http-foundation": "^6.0", + "symfony/http-kernel": "^6.0", + "symfony/mailer": "^6.0", + "symfony/mime": "^6.0", + "symfony/process": "^6.0", + "symfony/routing": "^6.0", + "symfony/uid": "^6.0", + "symfony/var-dumper": "^6.0", + "tijsverkoyen/css-to-inline-styles": "^2.2.5", "vlucas/phpdotenv": "^5.4.1", - "voku/portable-ascii": "^1.6.1" + "voku/portable-ascii": "^2.0" }, "conflict": { "tightenco/collect": "<5.5.33" }, "provide": { - "psr/container-implementation": "1.0", - "psr/simple-cache-implementation": "1.0" + "psr/container-implementation": "1.1|2.0", + "psr/simple-cache-implementation": "1.0|2.0|3.0" }, "replace": { "illuminate/auth": "self.version", @@ -1642,6 +1570,7 @@ "illuminate/bus": "self.version", "illuminate/cache": "self.version", "illuminate/collections": "self.version", + "illuminate/conditionable": "self.version", "illuminate/config": "self.version", "illuminate/console": "self.version", "illuminate/container": "self.version", @@ -1670,21 +1599,27 @@ "illuminate/view": "self.version" }, "require-dev": { - "aws/aws-sdk-php": "^3.198.1", + "ably/ably-php": "^1.0", + "aws/aws-sdk-php": "^3.235.5", "doctrine/dbal": "^2.13.3|^3.1.4", - "filp/whoops": "^2.14.3", - "guzzlehttp/guzzle": "^6.5.5|^7.0.1", - "league/flysystem-cached-adapter": "^1.0", - "mockery/mockery": "^1.4.4", - "orchestra/testbench-core": "^6.27", + "fakerphp/faker": "^1.9.2", + "guzzlehttp/guzzle": "^7.5", + "league/flysystem-aws-s3-v3": "^3.0", + "league/flysystem-ftp": "^3.0", + "league/flysystem-path-prefixing": "^3.3", + "league/flysystem-read-only": "^3.3", + "league/flysystem-sftp-v3": "^3.0", + "mockery/mockery": "^1.5.1", + "orchestra/testbench-core": "^7.8", "pda/pheanstalk": "^4.0", - "phpunit/phpunit": "^8.5.19|^9.5.8", - "predis/predis": "^1.1.9", - "symfony/cache": "^5.4" + "phpstan/phpstan": "^1.4.7", + "phpunit/phpunit": "^9.5.8", + "predis/predis": "^1.1.9|^2.0.2", + "symfony/cache": "^6.0" }, "suggest": { "ably/ably-php": "Required to use the Ably broadcast driver (^1.0).", - "aws/aws-sdk-php": "Required to use the SQS queue driver, DynamoDb failed job storage and SES mail driver (^3.198.1).", + "aws/aws-sdk-php": "Required to use the SQS queue driver, DynamoDb failed job storage, and SES mail driver (^3.235.5).", "brianium/paratest": "Required to run tests in parallel (^6.0).", "doctrine/dbal": "Required to rename columns and drop SQLite columns (^2.13.3|^3.1.4).", "ext-bcmath": "Required to use the multiple_of validation rule.", @@ -1696,27 +1631,31 @@ "ext-redis": "Required to use the Redis cache and queue drivers (^4.0|^5.0).", "fakerphp/faker": "Required to use the eloquent factory builder (^1.9.1).", "filp/whoops": "Required for friendly error pages in development (^2.14.3).", - "guzzlehttp/guzzle": "Required to use the HTTP Client, Mailgun mail driver and the ping methods on schedules (^6.5.5|^7.0.1).", + "guzzlehttp/guzzle": "Required to use the HTTP Client and the ping methods on schedules (^7.5).", "laravel/tinker": "Required to use the tinker console command (^2.0).", - "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (^1.0).", - "league/flysystem-cached-adapter": "Required to use the Flysystem cache (^1.0).", - "league/flysystem-sftp": "Required to use the Flysystem SFTP driver (^1.0).", - "mockery/mockery": "Required to use mocking (^1.4.4).", + "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (^3.0).", + "league/flysystem-ftp": "Required to use the Flysystem FTP driver (^3.0).", + "league/flysystem-path-prefixing": "Required to use the scoped driver (^3.3).", + "league/flysystem-read-only": "Required to use read-only disks (^3.3)", + "league/flysystem-sftp-v3": "Required to use the Flysystem SFTP driver (^3.0).", + "mockery/mockery": "Required to use mocking (^1.5.1).", "nyholm/psr7": "Required to use PSR-7 bridging features (^1.2).", "pda/pheanstalk": "Required to use the beanstalk queue driver (^4.0).", - "phpunit/phpunit": "Required to use assertions and run tests (^8.5.19|^9.5.8).", - "predis/predis": "Required to use the predis connector (^1.1.9).", + "phpunit/phpunit": "Required to use assertions and run tests (^9.5.8).", + "predis/predis": "Required to use the predis connector (^1.1.9|^2.0.2).", "psr/http-message": "Required to allow Storage::put to accept a StreamInterface (^1.0).", - "pusher/pusher-php-server": "Required to use the Pusher broadcast driver (^4.0|^5.0|^6.0|^7.0).", - "symfony/cache": "Required to PSR-6 cache bridge (^5.4).", - "symfony/filesystem": "Required to enable support for relative symbolic links (^5.4).", - "symfony/psr-http-message-bridge": "Required to use PSR-7 bridging features (^2.0).", - "wildbit/swiftmailer-postmark": "Required to use Postmark mail driver (^3.0)." + "pusher/pusher-php-server": "Required to use the Pusher broadcast driver (^6.0|^7.0).", + "symfony/cache": "Required to PSR-6 cache bridge (^6.0).", + "symfony/filesystem": "Required to enable support for relative symbolic links (^6.0).", + "symfony/http-client": "Required to enable support for the Symfony API mail transports (^6.0).", + "symfony/mailgun-mailer": "Required to enable support for the Mailgun mail transport (^6.0).", + "symfony/postmark-mailer": "Required to enable support for the Postmark mail transport (^6.0).", + "symfony/psr-http-message-bridge": "Required to use PSR-7 bridging features (^2.0)." }, "type": "library", "extra": { "branch-alias": { - "dev-master": "8.x-dev" + "dev-master": "9.x-dev" } }, "autoload": { @@ -1730,7 +1669,8 @@ "Illuminate\\": "src/Illuminate/", "Illuminate\\Support\\": [ "src/Illuminate/Macroable/", - "src/Illuminate/Collections/" + "src/Illuminate/Collections/", + "src/Illuminate/Conditionable/" ] } }, @@ -1754,7 +1694,7 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2022-05-24T14:04:02+00:00" + "time": "2022-10-04T13:33:43+00:00" }, { "name": "laravel/helpers", @@ -1879,25 +1819,26 @@ }, { "name": "laravel/serializable-closure", - "version": "v1.2.0", + "version": "v1.2.2", "source": { "type": "git", "url": "https://github.com/laravel/serializable-closure.git", - "reference": "09f0e9fb61829f628205b7c94906c28740ff9540" + "reference": "47afb7fae28ed29057fdca37e16a84f90cc62fae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/09f0e9fb61829f628205b7c94906c28740ff9540", - "reference": "09f0e9fb61829f628205b7c94906c28740ff9540", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/47afb7fae28ed29057fdca37e16a84f90cc62fae", + "reference": "47afb7fae28ed29057fdca37e16a84f90cc62fae", "shasum": "" }, "require": { "php": "^7.3|^8.0" }, "require-dev": { - "pestphp/pest": "^1.18", - "phpstan/phpstan": "^0.12.98", - "symfony/var-dumper": "^5.3" + "nesbot/carbon": "^2.61", + "pestphp/pest": "^1.21.3", + "phpstan/phpstan": "^1.8.2", + "symfony/var-dumper": "^5.4.11" }, "type": "library", "extra": { @@ -1934,7 +1875,7 @@ "issues": "https://github.com/laravel/serializable-closure/issues", "source": "https://github.com/laravel/serializable-closure" }, - "time": "2022-05-16T17:09:47+00:00" + "time": "2022-09-08T13:45:54+00:00" }, { "name": "laravel/tinker", @@ -2067,31 +2008,31 @@ }, { "name": "lcobucci/clock", - "version": "2.0.0", + "version": "2.2.0", "source": { "type": "git", "url": "https://github.com/lcobucci/clock.git", - "reference": "353d83fe2e6ae95745b16b3d911813df6a05bfb3" + "reference": "fb533e093fd61321bfcbac08b131ce805fe183d3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/lcobucci/clock/zipball/353d83fe2e6ae95745b16b3d911813df6a05bfb3", - "reference": "353d83fe2e6ae95745b16b3d911813df6a05bfb3", + "url": "https://api.github.com/repos/lcobucci/clock/zipball/fb533e093fd61321bfcbac08b131ce805fe183d3", + "reference": "fb533e093fd61321bfcbac08b131ce805fe183d3", "shasum": "" }, "require": { - "php": "^7.4 || ^8.0" + "php": "^8.0", + "stella-maris/clock": "^0.1.4" }, "require-dev": { - "infection/infection": "^0.17", - "lcobucci/coding-standard": "^6.0", - "phpstan/extension-installer": "^1.0", + "infection/infection": "^0.26", + "lcobucci/coding-standard": "^8.0", + "phpstan/extension-installer": "^1.1", "phpstan/phpstan": "^0.12", "phpstan/phpstan-deprecation-rules": "^0.12", "phpstan/phpstan-phpunit": "^0.12", "phpstan/phpstan-strict-rules": "^0.12", - "phpunit/php-code-coverage": "9.1.4", - "phpunit/phpunit": "9.3.7" + "phpunit/phpunit": "^9.5" }, "type": "library", "autoload": { @@ -2112,7 +2053,7 @@ "description": "Yet another clock abstraction", "support": { "issues": "https://github.com/lcobucci/clock/issues", - "source": "https://github.com/lcobucci/clock/tree/2.0.x" + "source": "https://github.com/lcobucci/clock/tree/2.2.0" }, "funding": [ { @@ -2124,20 +2065,20 @@ "type": "patreon" } ], - "time": "2020-08-27T18:56:02+00:00" + "time": "2022-04-19T19:34:17+00:00" }, { "name": "lcobucci/jwt", - "version": "4.1.5", + "version": "4.2.1", "source": { "type": "git", "url": "https://github.com/lcobucci/jwt.git", - "reference": "fe2d89f2eaa7087af4aa166c6f480ef04e000582" + "reference": "72ac6d807ee51a70ad376ee03a2387e8646e10f3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/lcobucci/jwt/zipball/fe2d89f2eaa7087af4aa166c6f480ef04e000582", - "reference": "fe2d89f2eaa7087af4aa166c6f480ef04e000582", + "url": "https://api.github.com/repos/lcobucci/jwt/zipball/72ac6d807ee51a70ad376ee03a2387e8646e10f3", + "reference": "72ac6d807ee51a70ad376ee03a2387e8646e10f3", "shasum": "" }, "require": { @@ -2153,12 +2094,12 @@ "infection/infection": "^0.21", "lcobucci/coding-standard": "^6.0", "mikey179/vfsstream": "^1.6.7", - "phpbench/phpbench": "^1.0", + "phpbench/phpbench": "^1.2", "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^0.12", - "phpstan/phpstan-deprecation-rules": "^0.12", - "phpstan/phpstan-phpunit": "^0.12", - "phpstan/phpstan-strict-rules": "^0.12", + "phpstan/phpstan": "^1.4", + "phpstan/phpstan-deprecation-rules": "^1.0", + "phpstan/phpstan-phpunit": "^1.0", + "phpstan/phpstan-strict-rules": "^1.0", "phpunit/php-invoker": "^3.1", "phpunit/phpunit": "^9.5" }, @@ -2186,7 +2127,7 @@ ], "support": { "issues": "https://github.com/lcobucci/jwt/issues", - "source": "https://github.com/lcobucci/jwt/tree/4.1.5" + "source": "https://github.com/lcobucci/jwt/tree/4.2.1" }, "funding": [ { @@ -2198,20 +2139,20 @@ "type": "patreon" } ], - "time": "2021-09-28T19:34:56+00:00" + "time": "2022-08-19T23:14:07+00:00" }, { "name": "league/commonmark", - "version": "2.3.1", + "version": "2.3.5", "source": { "type": "git", "url": "https://github.com/thephpleague/commonmark.git", - "reference": "cb36fee279f7fca01d5d9399ddd1b37e48e2eca1" + "reference": "84d74485fdb7074f4f9dd6f02ab957b1de513257" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/cb36fee279f7fca01d5d9399ddd1b37e48e2eca1", - "reference": "cb36fee279f7fca01d5d9399ddd1b37e48e2eca1", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/84d74485fdb7074f4f9dd6f02ab957b1de513257", + "reference": "84d74485fdb7074f4f9dd6f02ab957b1de513257", "shasum": "" }, "require": { @@ -2233,13 +2174,13 @@ "github/gfm": "0.29.0", "michelf/php-markdown": "^1.4", "nyholm/psr7": "^1.5", - "phpstan/phpstan": "^0.12.88 || ^1.0.0", - "phpunit/phpunit": "^9.5.5", + "phpstan/phpstan": "^1.8.2", + "phpunit/phpunit": "^9.5.21", "scrutinizer/ocular": "^1.8.1", - "symfony/finder": "^5.3", + "symfony/finder": "^5.3 | ^6.0", "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0", - "unleashedtech/php-coding-standard": "^3.1", - "vimeo/psalm": "^4.7.3" + "unleashedtech/php-coding-standard": "^3.1.1", + "vimeo/psalm": "^4.24.0" }, "suggest": { "symfony/yaml": "v2.3+ required if using the Front Matter extension" @@ -2304,7 +2245,7 @@ "type": "tidelift" } ], - "time": "2022-05-14T15:37:39+00:00" + "time": "2022-07-29T10:59:45+00:00" }, { "name": "league/config", @@ -2390,54 +2331,49 @@ }, { "name": "league/flysystem", - "version": "1.1.9", + "version": "3.5.2", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "094defdb4a7001845300334e7c1ee2335925ef99" + "reference": "c73c4eb31f2e883b3897ab5591aa2dbc48112433" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/094defdb4a7001845300334e7c1ee2335925ef99", - "reference": "094defdb4a7001845300334e7c1ee2335925ef99", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/c73c4eb31f2e883b3897ab5591aa2dbc48112433", + "reference": "c73c4eb31f2e883b3897ab5591aa2dbc48112433", "shasum": "" }, "require": { - "ext-fileinfo": "*", - "league/mime-type-detection": "^1.3", - "php": "^7.2.5 || ^8.0" + "league/mime-type-detection": "^1.0.0", + "php": "^8.0.2" }, "conflict": { - "league/flysystem-sftp": "<1.0.6" + "aws/aws-sdk-php": "3.209.31 || 3.210.0", + "guzzlehttp/guzzle": "<7.0", + "guzzlehttp/ringphp": "<1.1.1", + "phpseclib/phpseclib": "3.0.15", + "symfony/http-client": "<5.2" }, "require-dev": { - "phpspec/prophecy": "^1.11.1", - "phpunit/phpunit": "^8.5.8" - }, - "suggest": { - "ext-ftp": "Allows you to use FTP server storage", - "ext-openssl": "Allows you to use FTPS server storage", - "league/flysystem-aws-s3-v2": "Allows you to use S3 storage with AWS SDK v2", - "league/flysystem-aws-s3-v3": "Allows you to use S3 storage with AWS SDK v3", - "league/flysystem-azure": "Allows you to use Windows Azure Blob storage", - "league/flysystem-cached-adapter": "Flysystem adapter decorator for metadata caching", - "league/flysystem-eventable-filesystem": "Allows you to use EventableFilesystem", - "league/flysystem-rackspace": "Allows you to use Rackspace Cloud Files", - "league/flysystem-sftp": "Allows you to use SFTP server storage via phpseclib", - "league/flysystem-webdav": "Allows you to use WebDAV storage", - "league/flysystem-ziparchive": "Allows you to use ZipArchive adapter", - "spatie/flysystem-dropbox": "Allows you to use Dropbox storage", - "srmklive/flysystem-dropbox-v2": "Allows you to use Dropbox storage for PHP 5 applications" + "async-aws/s3": "^1.5", + "async-aws/simple-s3": "^1.0", + "aws/aws-sdk-php": "^3.198.1", + "composer/semver": "^3.0", + "ext-fileinfo": "*", + "ext-ftp": "*", + "ext-zip": "*", + "friendsofphp/php-cs-fixer": "^3.5", + "google/cloud-storage": "^1.23", + "microsoft/azure-storage-blob": "^1.1", + "phpseclib/phpseclib": "^3.0.14", + "phpstan/phpstan": "^0.12.26", + "phpunit/phpunit": "^9.5.11", + "sabre/dav": "^4.3.1" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.1-dev" - } - }, "autoload": { "psr-4": { - "League\\Flysystem\\": "src/" + "League\\Flysystem\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -2447,73 +2383,71 @@ "authors": [ { "name": "Frank de Jonge", - "email": "info@frenky.net" + "email": "info@frankdejonge.nl" } ], - "description": "Filesystem abstraction: Many filesystems, one API.", + "description": "File storage abstraction for PHP", "keywords": [ - "Cloud Files", "WebDAV", - "abstraction", "aws", "cloud", - "copy.com", - "dropbox", - "file systems", + "file", "files", "filesystem", "filesystems", "ftp", - "rackspace", - "remote", "s3", "sftp", "storage" ], "support": { "issues": "https://github.com/thephpleague/flysystem/issues", - "source": "https://github.com/thephpleague/flysystem/tree/1.1.9" + "source": "https://github.com/thephpleague/flysystem/tree/3.5.2" }, "funding": [ { - "url": "https://offset.earth/frankdejonge", - "type": "other" + "url": "https://ecologi.com/frankdejonge", + "type": "custom" + }, + { + "url": "https://github.com/frankdejonge", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/league/flysystem", + "type": "tidelift" } ], - "time": "2021-12-09T09:40:50+00:00" + "time": "2022-09-23T18:59:16+00:00" }, { "name": "league/flysystem-aws-s3-v3", - "version": "1.0.29", + "version": "3.5.0", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem-aws-s3-v3.git", - "reference": "4e25cc0582a36a786c31115e419c6e40498f6972" + "reference": "adb6633f325c934c15a099c363dc5362bdcb07a2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/4e25cc0582a36a786c31115e419c6e40498f6972", - "reference": "4e25cc0582a36a786c31115e419c6e40498f6972", + "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/adb6633f325c934c15a099c363dc5362bdcb07a2", + "reference": "adb6633f325c934c15a099c363dc5362bdcb07a2", "shasum": "" }, "require": { - "aws/aws-sdk-php": "^3.20.0", - "league/flysystem": "^1.0.40", - "php": ">=5.5.0" + "aws/aws-sdk-php": "^3.132.4", + "league/flysystem": "^3.0.0", + "league/mime-type-detection": "^1.0.0", + "php": "^8.0.2" }, - "require-dev": { - "henrikbjorn/phpspec-code-coverage": "~1.0.1", - "phpspec/phpspec": "^2.0.0" + "conflict": { + "guzzlehttp/guzzle": "<7.0", + "guzzlehttp/ringphp": "<1.1.1" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, "autoload": { "psr-4": { - "League\\Flysystem\\AwsS3v3\\": "src/" + "League\\Flysystem\\AwsS3V3\\": "" } }, "notification-url": "https://packagist.org/downloads/", @@ -2523,45 +2457,62 @@ "authors": [ { "name": "Frank de Jonge", - "email": "info@frenky.net" + "email": "info@frankdejonge.nl" } ], - "description": "Flysystem adapter for the AWS S3 SDK v3.x", + "description": "AWS S3 filesystem adapter for Flysystem.", + "keywords": [ + "Flysystem", + "aws", + "file", + "files", + "filesystem", + "s3", + "storage" + ], "support": { "issues": "https://github.com/thephpleague/flysystem-aws-s3-v3/issues", - "source": "https://github.com/thephpleague/flysystem-aws-s3-v3/tree/1.0.29" + "source": "https://github.com/thephpleague/flysystem-aws-s3-v3/tree/3.5.0" }, - "time": "2020-10-08T18:58:37+00:00" + "funding": [ + { + "url": "https://ecologi.com/frankdejonge", + "type": "custom" + }, + { + "url": "https://github.com/frankdejonge", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/league/flysystem", + "type": "tidelift" + } + ], + "time": "2022-09-17T21:00:35+00:00" }, { "name": "league/flysystem-memory", - "version": "1.0.2", + "version": "3.3.0", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem-memory.git", - "reference": "d0e87477c32e29f999b4de05e64c1adcddb51757" + "reference": "d2a80fbfd3337fac39eeff64c6c0820b6a4902ce" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem-memory/zipball/d0e87477c32e29f999b4de05e64c1adcddb51757", - "reference": "d0e87477c32e29f999b4de05e64c1adcddb51757", + "url": "https://api.github.com/repos/thephpleague/flysystem-memory/zipball/d2a80fbfd3337fac39eeff64c6c0820b6a4902ce", + "reference": "d2a80fbfd3337fac39eeff64c6c0820b6a4902ce", "shasum": "" }, "require": { - "league/flysystem": "~1.0" - }, - "require-dev": { - "phpunit/phpunit": "^5.7.10" + "ext-fileinfo": "*", + "league/flysystem": "^2.0.0 || ^3.0.0", + "php": "^8.0.2" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, "autoload": { "psr-4": { - "League\\Flysystem\\Memory\\": "src/" + "League\\Flysystem\\InMemory\\": "" } }, "notification-url": "https://packagist.org/downloads/", @@ -2570,23 +2521,37 @@ ], "authors": [ { - "name": "Chris Leppanen", - "email": "chris.leppanen@gmail.com", - "role": "Developer" + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" } ], - "description": "An in-memory adapter for Flysystem.", - "homepage": "https://github.com/thephpleague/flysystem-memory", + "description": "In-memory filesystem adapter for Flysystem.", "keywords": [ "Flysystem", - "adapter", + "file", + "files", + "filesystem", "memory" ], "support": { "issues": "https://github.com/thephpleague/flysystem-memory/issues", - "source": "https://github.com/thephpleague/flysystem-memory/tree/1.0.2" + "source": "https://github.com/thephpleague/flysystem-memory/tree/3.3.0" }, - "time": "2019-05-30T21:34:13+00:00" + "funding": [ + { + "url": "https://ecologi.com/frankdejonge", + "type": "custom" + }, + { + "url": "https://github.com/frankdejonge", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/league/flysystem", + "type": "tidelift" + } + ], + "time": "2022-09-09T10:03:42+00:00" }, { "name": "league/fractal", @@ -2764,16 +2729,16 @@ }, { "name": "monolog/monolog", - "version": "2.6.0", + "version": "2.8.0", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "247918972acd74356b0a91dfaa5adcaec069b6c0" + "reference": "720488632c590286b88b80e62aa3d3d551ad4a50" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/247918972acd74356b0a91dfaa5adcaec069b6c0", - "reference": "247918972acd74356b0a91dfaa5adcaec069b6c0", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/720488632c590286b88b80e62aa3d3d551ad4a50", + "reference": "720488632c590286b88b80e62aa3d3d551ad4a50", "shasum": "" }, "require": { @@ -2793,11 +2758,10 @@ "guzzlehttp/psr7": "^2.2", "mongodb/mongodb": "^1.8", "php-amqplib/php-amqplib": "~2.4 || ^3", - "php-console/php-console": "^3.1.3", "phpspec/prophecy": "^1.15", "phpstan/phpstan": "^0.12.91", "phpunit/phpunit": "^8.5.14", - "predis/predis": "^1.1", + "predis/predis": "^1.1 || ^2.0", "rollbar/rollbar": "^1.3 || ^2 || ^3", "ruflin/elastica": "^7", "swiftmailer/swiftmailer": "^5.3|^6.0", @@ -2817,7 +2781,6 @@ "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)", "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", - "php-console/php-console": "Allow sending log messages to Google Chrome", "rollbar/rollbar": "Allow sending log messages to Rollbar", "ruflin/elastica": "Allow sending log messages to an Elastic Search server" }, @@ -2852,7 +2815,7 @@ ], "support": { "issues": "https://github.com/Seldaek/monolog/issues", - "source": "https://github.com/Seldaek/monolog/tree/2.6.0" + "source": "https://github.com/Seldaek/monolog/tree/2.8.0" }, "funding": [ { @@ -2864,7 +2827,7 @@ "type": "tidelift" } ], - "time": "2022-05-10T09:36:00+00:00" + "time": "2022-07-24T11:55:47+00:00" }, { "name": "mtdowling/jmespath.php", @@ -2929,16 +2892,16 @@ }, { "name": "nesbot/carbon", - "version": "2.58.0", + "version": "2.62.1", "source": { "type": "git", "url": "https://github.com/briannesbitt/Carbon.git", - "reference": "97a34af22bde8d0ac20ab34b29d7bfe360902055" + "reference": "01bc4cdefe98ef58d1f9cb31bdbbddddf2a88f7a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/97a34af22bde8d0ac20ab34b29d7bfe360902055", - "reference": "97a34af22bde8d0ac20ab34b29d7bfe360902055", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/01bc4cdefe98ef58d1f9cb31bdbbddddf2a88f7a", + "reference": "01bc4cdefe98ef58d1f9cb31bdbbddddf2a88f7a", "shasum": "" }, "require": { @@ -2953,11 +2916,12 @@ "doctrine/orm": "^2.7", "friendsofphp/php-cs-fixer": "^3.0", "kylekatarnls/multi-tester": "^2.0", + "ondrejmirtes/better-reflection": "*", "phpmd/phpmd": "^2.9", "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^0.12.54 || ^1.0", - "phpunit/php-file-iterator": "^2.0.5", - "phpunit/phpunit": "^7.5.20 || ^8.5.23", + "phpstan/phpstan": "^0.12.99 || ^1.7.14", + "phpunit/php-file-iterator": "^2.0.5 || ^3.0.6", + "phpunit/phpunit": "^7.5.20 || ^8.5.26 || ^9.5.20", "squizlabs/php_codesniffer": "^3.4" }, "bin": [ @@ -3014,15 +2978,19 @@ }, "funding": [ { - "url": "https://opencollective.com/Carbon", - "type": "open_collective" + "url": "https://github.com/sponsors/kylekatarnls", + "type": "github" }, { - "url": "https://tidelift.com/funding/github/packagist/nesbot/carbon", + "url": "https://opencollective.com/Carbon#sponsor", + "type": "opencollective" + }, + { + "url": "https://tidelift.com/subscription/pkg/packagist-nesbot-carbon?utm_source=packagist-nesbot-carbon&utm_medium=referral&utm_campaign=readme", "type": "tidelift" } ], - "time": "2022-04-25T19:31:17+00:00" + "time": "2022-09-02T07:48:13+00:00" }, { "name": "nette/schema", @@ -3088,20 +3056,20 @@ }, { "name": "nette/utils", - "version": "v3.2.7", + "version": "v3.2.8", "source": { "type": "git", "url": "https://github.com/nette/utils.git", - "reference": "0af4e3de4df9f1543534beab255ccf459e7a2c99" + "reference": "02a54c4c872b99e4ec05c4aec54b5a06eb0f6368" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/utils/zipball/0af4e3de4df9f1543534beab255ccf459e7a2c99", - "reference": "0af4e3de4df9f1543534beab255ccf459e7a2c99", + "url": "https://api.github.com/repos/nette/utils/zipball/02a54c4c872b99e4ec05c4aec54b5a06eb0f6368", + "reference": "02a54c4c872b99e4ec05c4aec54b5a06eb0f6368", "shasum": "" }, "require": { - "php": ">=7.2 <8.2" + "php": ">=7.2 <8.3" }, "conflict": { "nette/di": "<3.0.6" @@ -3167,22 +3135,22 @@ ], "support": { "issues": "https://github.com/nette/utils/issues", - "source": "https://github.com/nette/utils/tree/v3.2.7" + "source": "https://github.com/nette/utils/tree/v3.2.8" }, - "time": "2022-01-24T11:29:14+00:00" + "time": "2022-09-12T23:36:20+00:00" }, { "name": "nikic/php-parser", - "version": "v4.13.2", + "version": "v4.15.1", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "210577fe3cf7badcc5814d99455df46564f3c077" + "reference": "0ef6c55a3f47f89d7a374e6f835197a0b5fcf900" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/210577fe3cf7badcc5814d99455df46564f3c077", - "reference": "210577fe3cf7badcc5814d99455df46564f3c077", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/0ef6c55a3f47f89d7a374e6f835197a0b5fcf900", + "reference": "0ef6c55a3f47f89d7a374e6f835197a0b5fcf900", "shasum": "" }, "require": { @@ -3223,43 +3191,55 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.13.2" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.1" }, - "time": "2021-11-30T19:35:32+00:00" + "time": "2022-09-04T07:30:47+00:00" }, { - "name": "opis/closure", - "version": "3.6.3", + "name": "nunomaduro/termwind", + "version": "v1.14.0", "source": { "type": "git", - "url": "https://github.com/opis/closure.git", - "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad" + "url": "https://github.com/nunomaduro/termwind.git", + "reference": "10065367baccf13b6e30f5e9246fa4f63a79eb1d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opis/closure/zipball/3d81e4309d2a927abbe66df935f4bb60082805ad", - "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad", + "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/10065367baccf13b6e30f5e9246fa4f63a79eb1d", + "reference": "10065367baccf13b6e30f5e9246fa4f63a79eb1d", "shasum": "" }, "require": { - "php": "^5.4 || ^7.0 || ^8.0" + "ext-mbstring": "*", + "php": "^8.0", + "symfony/console": "^5.3.0|^6.0.0" }, "require-dev": { - "jeremeamia/superclosure": "^2.0", - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" + "ergebnis/phpstan-rules": "^1.0.", + "illuminate/console": "^8.0|^9.0", + "illuminate/support": "^8.0|^9.0", + "laravel/pint": "^1.0.0", + "pestphp/pest": "^1.21.0", + "pestphp/pest-plugin-mock": "^1.0", + "phpstan/phpstan": "^1.4.6", + "phpstan/phpstan-strict-rules": "^1.1.0", + "symfony/var-dumper": "^5.2.7|^6.0.0", + "thecodingmachine/phpstan-strict-rules": "^1.0.0" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "3.6.x-dev" + "laravel": { + "providers": [ + "Termwind\\Laravel\\TermwindServiceProvider" + ] } }, "autoload": { "files": [ - "functions.php" + "src/Functions.php" ], "psr-4": { - "Opis\\Closure\\": "src/" + "Termwind\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -3268,42 +3248,51 @@ ], "authors": [ { - "name": "Marius Sarca", - "email": "marius.sarca@gmail.com" - }, - { - "name": "Sorin Sarca", - "email": "sarca_sorin@hotmail.com" + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" } ], - "description": "A library that can be used to serialize closures (anonymous functions) and arbitrary objects.", - "homepage": "https://opis.io/closure", + "description": "Its like Tailwind CSS, but for the console.", "keywords": [ - "anonymous functions", - "closure", - "function", - "serializable", - "serialization", - "serialize" + "cli", + "console", + "css", + "package", + "php", + "style" ], "support": { - "issues": "https://github.com/opis/closure/issues", - "source": "https://github.com/opis/closure/tree/3.6.3" + "issues": "https://github.com/nunomaduro/termwind/issues", + "source": "https://github.com/nunomaduro/termwind/tree/v1.14.0" }, - "time": "2022-01-27T09:35:39+00:00" + "funding": [ + { + "url": "https://www.paypal.com/paypalme/enunomaduro", + "type": "custom" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" + }, + { + "url": "https://github.com/xiCO2k", + "type": "github" + } + ], + "time": "2022-08-01T11:03:24+00:00" }, { "name": "paragonie/constant_time_encoding", - "version": "v2.5.0", + "version": "v2.6.3", "source": { "type": "git", "url": "https://github.com/paragonie/constant_time_encoding.git", - "reference": "9229e15f2e6ba772f0c55dd6986c563b937170a8" + "reference": "58c3f47f650c94ec05a151692652a868995d2938" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/9229e15f2e6ba772f0c55dd6986c563b937170a8", - "reference": "9229e15f2e6ba772f0c55dd6986c563b937170a8", + "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/58c3f47f650c94ec05a151692652a868995d2938", + "reference": "58c3f47f650c94ec05a151692652a868995d2938", "shasum": "" }, "require": { @@ -3357,7 +3346,7 @@ "issues": "https://github.com/paragonie/constant_time_encoding/issues", "source": "https://github.com/paragonie/constant_time_encoding" }, - "time": "2022-01-17T05:32:27+00:00" + "time": "2022-06-14T06:56:20+00:00" }, { "name": "paragonie/random_compat", @@ -3411,29 +3400,33 @@ }, { "name": "phpoption/phpoption", - "version": "1.8.1", + "version": "1.9.0", "source": { "type": "git", "url": "https://github.com/schmittjoh/php-option.git", - "reference": "eab7a0df01fe2344d172bff4cd6dbd3f8b84ad15" + "reference": "dc5ff11e274a90cc1c743f66c9ad700ce50db9ab" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/eab7a0df01fe2344d172bff4cd6dbd3f8b84ad15", - "reference": "eab7a0df01fe2344d172bff4cd6dbd3f8b84ad15", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/dc5ff11e274a90cc1c743f66c9ad700ce50db9ab", + "reference": "dc5ff11e274a90cc1c743f66c9ad700ce50db9ab", "shasum": "" }, "require": { - "php": "^7.0 || ^8.0" + "php": "^7.2.5 || ^8.0" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.4.1", - "phpunit/phpunit": "^6.5.14 || ^7.5.20 || ^8.5.19 || ^9.5.8" + "bamarni/composer-bin-plugin": "^1.8", + "phpunit/phpunit": "^8.5.28 || ^9.5.21" }, "type": "library", "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": true + }, "branch-alias": { - "dev-master": "1.8-dev" + "dev-master": "1.9-dev" } }, "autoload": { @@ -3466,7 +3459,7 @@ ], "support": { "issues": "https://github.com/schmittjoh/php-option/issues", - "source": "https://github.com/schmittjoh/php-option/tree/1.8.1" + "source": "https://github.com/schmittjoh/php-option/tree/1.9.0" }, "funding": [ { @@ -3478,20 +3471,20 @@ "type": "tidelift" } ], - "time": "2021-12-04T23:24:31+00:00" + "time": "2022-07-30T15:51:26+00:00" }, { "name": "phpseclib/phpseclib", - "version": "3.0.14", + "version": "3.0.16", "source": { "type": "git", "url": "https://github.com/phpseclib/phpseclib.git", - "reference": "2f0b7af658cbea265cbb4a791d6c29a6613f98ef" + "reference": "7181378909ed8890be4db53d289faac5b77f8b05" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/2f0b7af658cbea265cbb4a791d6c29a6613f98ef", - "reference": "2f0b7af658cbea265cbb4a791d6c29a6613f98ef", + "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/7181378909ed8890be4db53d289faac5b77f8b05", + "reference": "7181378909ed8890be4db53d289faac5b77f8b05", "shasum": "" }, "require": { @@ -3503,6 +3496,7 @@ "phpunit/phpunit": "*" }, "suggest": { + "ext-dom": "Install the DOM extension to load XML formatted public keys.", "ext-gmp": "Install the GMP (GNU Multiple Precision) extension in order to speed up arbitrary precision integer arithmetic operations.", "ext-libsodium": "SSH2/SFTP can make use of some algorithms provided by the libsodium-php extension.", "ext-mcrypt": "Install the Mcrypt extension in order to speed up a few other cryptographic operations.", @@ -3571,7 +3565,7 @@ ], "support": { "issues": "https://github.com/phpseclib/phpseclib/issues", - "source": "https://github.com/phpseclib/phpseclib/tree/3.0.14" + "source": "https://github.com/phpseclib/phpseclib/tree/3.0.16" }, "funding": [ { @@ -3587,7 +3581,7 @@ "type": "tidelift" } ], - "time": "2022-04-04T05:15:45+00:00" + "time": "2022-09-05T18:03:08+00:00" }, { "name": "pragmarx/google2fa", @@ -3651,29 +3645,34 @@ }, { "name": "predis/predis", - "version": "v1.1.10", + "version": "v2.0.2", "source": { "type": "git", "url": "https://github.com/predis/predis.git", - "reference": "a2fb02d738bedadcffdbb07efa3a5e7bd57f8d6e" + "reference": "8b5fa928560b48a054fb1fd485fc65f2d8aa9e5c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/predis/predis/zipball/a2fb02d738bedadcffdbb07efa3a5e7bd57f8d6e", - "reference": "a2fb02d738bedadcffdbb07efa3a5e7bd57f8d6e", + "url": "https://api.github.com/repos/predis/predis/zipball/8b5fa928560b48a054fb1fd485fc65f2d8aa9e5c", + "reference": "8b5fa928560b48a054fb1fd485fc65f2d8aa9e5c", "shasum": "" }, "require": { - "php": ">=5.3.9" + "php": "^7.2 || ^8.0" }, "require-dev": { - "phpunit/phpunit": "~4.8" + "phpunit/phpunit": "^8.0 || ~9.4.4" }, "suggest": { "ext-curl": "Allows access to Webdis when paired with phpiredis", "ext-phpiredis": "Allows faster serialization and deserialization of the Redis protocol" }, "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.0-dev" + } + }, "autoload": { "psr-4": { "Predis\\": "src/" @@ -3696,7 +3695,7 @@ "role": "Maintainer" } ], - "description": "Flexible and feature-complete Redis client for PHP and HHVM", + "description": "A flexible and feature-complete Redis client for PHP.", "homepage": "http://github.com/predis/predis", "keywords": [ "nosql", @@ -3705,7 +3704,7 @@ ], "support": { "issues": "https://github.com/predis/predis/issues", - "source": "https://github.com/predis/predis/tree/v1.1.10" + "source": "https://github.com/predis/predis/tree/v2.0.2" }, "funding": [ { @@ -3713,27 +3712,26 @@ "type": "github" } ], - "time": "2022-01-05T17:46:08+00:00" + "time": "2022-09-06T14:34:14+00:00" }, { "name": "prologue/alerts", - "version": "0.4.8", + "version": "1.0.0", "source": { "type": "git", "url": "https://github.com/prologuephp/alerts.git", - "reference": "a6412e318c0171526bc8b25ef597f2cc61f5b800" + "reference": "b2880e28814b8dba8768e60e00511627381efe14" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/prologuephp/alerts/zipball/a6412e318c0171526bc8b25ef597f2cc61f5b800", - "reference": "a6412e318c0171526bc8b25ef597f2cc61f5b800", + "url": "https://api.github.com/repos/prologuephp/alerts/zipball/b2880e28814b8dba8768e60e00511627381efe14", + "reference": "b2880e28814b8dba8768e60e00511627381efe14", "shasum": "" }, "require": { - "illuminate/config": "~5|~6|~7|~8", - "illuminate/session": "~5|~6|~7|~8", - "illuminate/support": "~5|~6|~7|~8", - "php": ">=5.4.0" + "illuminate/config": "~9", + "illuminate/session": "~9", + "illuminate/support": "~9" }, "require-dev": { "mockery/mockery": "~0.9", @@ -3781,26 +3779,26 @@ ], "support": { "issues": "https://github.com/prologuephp/alerts/issues", - "source": "https://github.com/prologuephp/alerts/tree/master" + "source": "https://github.com/prologuephp/alerts/tree/1.0.0" }, - "time": "2020-09-08T14:24:39+00:00" + "time": "2022-01-19T09:28:45+00:00" }, { "name": "psr/cache", - "version": "1.0.1", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/php-fig/cache.git", - "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8" + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/cache/zipball/d11b50ad223250cf17b86e38383413f5a6764bf8", - "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8", + "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": ">=8.0.0" }, "type": "library", "extra": { @@ -3820,7 +3818,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], "description": "Common interface for caching libraries", @@ -3830,28 +3828,33 @@ "psr-6" ], "support": { - "source": "https://github.com/php-fig/cache/tree/master" + "source": "https://github.com/php-fig/cache/tree/3.0.0" }, - "time": "2016-08-06T20:24:11+00:00" + "time": "2021-02-03T23:26:27+00:00" }, { "name": "psr/container", - "version": "1.1.2", + "version": "2.0.2", "source": { "type": "git", "url": "https://github.com/php-fig/container.git", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", "shasum": "" }, "require": { "php": ">=7.4.0" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, "autoload": { "psr-4": { "Psr\\Container\\": "src/" @@ -3878,9 +3881,9 @@ ], "support": { "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/1.1.2" + "source": "https://github.com/php-fig/container/tree/2.0.2" }, - "time": "2021-11-05T16:50:12+00:00" + "time": "2021-11-05T16:47:00+00:00" }, { "name": "psr/event-dispatcher", @@ -4094,30 +4097,30 @@ }, { "name": "psr/log", - "version": "1.1.4", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "d49695b909c3b7628b6289db5479a1c204601f11" + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", - "reference": "d49695b909c3b7628b6289db5479a1c204601f11", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": ">=8.0.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1.x-dev" + "dev-master": "3.x-dev" } }, "autoload": { "psr-4": { - "Psr\\Log\\": "Psr/Log/" + "Psr\\Log\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -4138,31 +4141,31 @@ "psr-3" ], "support": { - "source": "https://github.com/php-fig/log/tree/1.1.4" + "source": "https://github.com/php-fig/log/tree/3.0.0" }, - "time": "2021-05-03T11:20:27+00:00" + "time": "2021-07-14T16:46:02+00:00" }, { "name": "psr/simple-cache", - "version": "1.0.1", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/php-fig/simple-cache.git", - "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b" + "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", - "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", + "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/764e0b3939f5ca87cb904f570ef9be2d78a07865", + "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": ">=8.0.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "3.0.x-dev" } }, "autoload": { @@ -4177,7 +4180,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], "description": "Common interfaces for simple caching", @@ -4189,22 +4192,22 @@ "simple-cache" ], "support": { - "source": "https://github.com/php-fig/simple-cache/tree/master" + "source": "https://github.com/php-fig/simple-cache/tree/3.0.0" }, - "time": "2017-10-23T01:57:42+00:00" + "time": "2021-10-29T13:26:27+00:00" }, { "name": "psy/psysh", - "version": "v0.11.5", + "version": "v0.11.8", "source": { "type": "git", "url": "https://github.com/bobthecow/psysh.git", - "reference": "c23686f9c48ca202710dbb967df8385a952a2daf" + "reference": "f455acf3645262ae389b10e9beba0c358aa6994e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bobthecow/psysh/zipball/c23686f9c48ca202710dbb967df8385a952a2daf", - "reference": "c23686f9c48ca202710dbb967df8385a952a2daf", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/f455acf3645262ae389b10e9beba0c358aa6994e", + "reference": "f455acf3645262ae389b10e9beba0c358aa6994e", "shasum": "" }, "require": { @@ -4265,9 +4268,9 @@ ], "support": { "issues": "https://github.com/bobthecow/psysh/issues", - "source": "https://github.com/bobthecow/psysh/tree/v0.11.5" + "source": "https://github.com/bobthecow/psysh/tree/v0.11.8" }, - "time": "2022-05-27T18:03:49+00:00" + "time": "2022-07-28T14:25:11+00:00" }, { "name": "ralouphie/getallheaders", @@ -4394,25 +4397,24 @@ }, { "name": "ramsey/uuid", - "version": "4.2.3", + "version": "4.5.1", "source": { "type": "git", "url": "https://github.com/ramsey/uuid.git", - "reference": "fc9bb7fb5388691fd7373cd44dcb4d63bbcf24df" + "reference": "a161a26d917604dc6d3aa25100fddf2556e9f35d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/fc9bb7fb5388691fd7373cd44dcb4d63bbcf24df", - "reference": "fc9bb7fb5388691fd7373cd44dcb4d63bbcf24df", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/a161a26d917604dc6d3aa25100fddf2556e9f35d", + "reference": "a161a26d917604dc6d3aa25100fddf2556e9f35d", "shasum": "" }, "require": { - "brick/math": "^0.8 || ^0.9", + "brick/math": "^0.8.8 || ^0.9 || ^0.10", + "ext-ctype": "*", "ext-json": "*", - "php": "^7.2 || ^8.0", - "ramsey/collection": "^1.0", - "symfony/polyfill-ctype": "^1.8", - "symfony/polyfill-php80": "^1.14" + "php": "^8.0", + "ramsey/collection": "^1.0" }, "replace": { "rhumsaa/uuid": "self.version" @@ -4424,18 +4426,18 @@ "doctrine/annotations": "^1.8", "ergebnis/composer-normalize": "^2.15", "mockery/mockery": "^1.3", - "moontoast/math": "^1.1", "paragonie/random-lib": "^2", "php-mock/php-mock": "^2.2", "php-mock/php-mock-mockery": "^1.3", "php-parallel-lint/php-parallel-lint": "^1.1", "phpbench/phpbench": "^1.0", - "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^0.12", - "phpstan/phpstan-mockery": "^0.12", - "phpstan/phpstan-phpunit": "^0.12", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-mockery": "^1.1", + "phpstan/phpstan-phpunit": "^1.1", "phpunit/phpunit": "^8.5 || ^9", - "slevomat/coding-standard": "^7.0", + "ramsey/composer-repl": "^1.4", + "slevomat/coding-standard": "^8.4", "squizlabs/php_codesniffer": "^3.5", "vimeo/psalm": "^4.9" }, @@ -4449,9 +4451,6 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "4.x-dev" - }, "captainhook": { "force-install": true } @@ -4476,7 +4475,7 @@ ], "support": { "issues": "https://github.com/ramsey/uuid/issues", - "source": "https://github.com/ramsey/uuid/tree/4.2.3" + "source": "https://github.com/ramsey/uuid/tree/4.5.1" }, "funding": [ { @@ -4488,7 +4487,7 @@ "type": "tidelift" } ], - "time": "2021-09-25T23:10:38+00:00" + "time": "2022-09-16T03:22:46+00:00" }, { "name": "s1lentium/iptools", @@ -4608,27 +4607,28 @@ }, { "name": "spatie/laravel-fractal", - "version": "5.8.1", + "version": "6.0.2", "source": { "type": "git", "url": "https://github.com/spatie/laravel-fractal.git", - "reference": "be3ccd54e26742cd05b3637fb732fd9addfa28df" + "reference": "1e6b8114389fe8e55eec3a841db02c404458f869" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-fractal/zipball/be3ccd54e26742cd05b3637fb732fd9addfa28df", - "reference": "be3ccd54e26742cd05b3637fb732fd9addfa28df", + "url": "https://api.github.com/repos/spatie/laravel-fractal/zipball/1e6b8114389fe8e55eec3a841db02c404458f869", + "reference": "1e6b8114389fe8e55eec3a841db02c404458f869", "shasum": "" }, "require": { - "illuminate/contracts": "^7.0|^8.0", - "illuminate/support": "^7.0|^8.0", - "php": "^7.2|^8.0", - "spatie/fractalistic": "^2.5" + "illuminate/contracts": "^8.0|^9.0", + "illuminate/support": "^8.0|^9.0", + "php": "^8.0", + "spatie/fractalistic": "^2.5", + "spatie/laravel-package-tools": "^1.11" }, "require-dev": { "ext-json": "*", - "orchestra/testbench": "^5.0|^6.0" + "orchestra/testbench": "^7.0" }, "type": "library", "extra": { @@ -4637,7 +4637,7 @@ "Spatie\\Fractal\\FractalServiceProvider" ], "aliases": { - "Fractal": "Spatie\\Fractal\\FractalFacade" + "Fractal": "Spatie\\Fractal\\Facades\\Fractal" } } }, @@ -4674,7 +4674,7 @@ ], "support": { "issues": "https://github.com/spatie/laravel-fractal/issues", - "source": "https://github.com/spatie/laravel-fractal/tree/5.8.1" + "source": "https://github.com/spatie/laravel-fractal/tree/6.0.2" }, "funding": [ { @@ -4682,34 +4682,94 @@ "type": "custom" } ], - "time": "2020-11-12T18:46:53+00:00" + "time": "2022-04-04T07:53:13+00:00" + }, + { + "name": "spatie/laravel-package-tools", + "version": "1.13.5", + "source": { + "type": "git", + "url": "https://github.com/spatie/laravel-package-tools.git", + "reference": "163ee3bc6c0a987535d8a99722a7dbcc5471a140" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/163ee3bc6c0a987535d8a99722a7dbcc5471a140", + "reference": "163ee3bc6c0a987535d8a99722a7dbcc5471a140", + "shasum": "" + }, + "require": { + "illuminate/contracts": "^9.28", + "php": "^8.0" + }, + "require-dev": { + "mockery/mockery": "^1.5", + "orchestra/testbench": "^7.7", + "phpunit/phpunit": "^9.5.24", + "spatie/test-time": "^1.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Spatie\\LaravelPackageTools\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "role": "Developer" + } + ], + "description": "Tools for creating Laravel packages", + "homepage": "https://github.com/spatie/laravel-package-tools", + "keywords": [ + "laravel-package-tools", + "spatie" + ], + "support": { + "issues": "https://github.com/spatie/laravel-package-tools/issues", + "source": "https://github.com/spatie/laravel-package-tools/tree/1.13.5" + }, + "funding": [ + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2022-09-07T14:31:31+00:00" }, { "name": "spatie/laravel-query-builder", - "version": "3.6.2", + "version": "5.0.3", "source": { "type": "git", "url": "https://github.com/spatie/laravel-query-builder.git", - "reference": "3768381e9f2f80b01f0088eca49f061c269e0e8b" + "reference": "2243e3d60fc184ef20ad2b19765bc7006fa9dbfc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-query-builder/zipball/3768381e9f2f80b01f0088eca49f061c269e0e8b", - "reference": "3768381e9f2f80b01f0088eca49f061c269e0e8b", + "url": "https://api.github.com/repos/spatie/laravel-query-builder/zipball/2243e3d60fc184ef20ad2b19765bc7006fa9dbfc", + "reference": "2243e3d60fc184ef20ad2b19765bc7006fa9dbfc", "shasum": "" }, "require": { - "illuminate/database": "^6.20.13|^7.30.4|^8.22.2", - "illuminate/http": "^6.20.13|^7.30.4|^8.22.2", - "illuminate/support": "^6.20.13|^7.30.4|^8.22.2", - "php": "^7.3|^8.0" + "illuminate/database": "^9.0", + "illuminate/http": "^9.0", + "illuminate/support": "^9.0", + "php": "^8.0", + "spatie/laravel-package-tools": "^1.11" }, "require-dev": { "ext-json": "*", - "laravel/legacy-factories": "^1.0.4", "mockery/mockery": "^1.4", - "orchestra/testbench": "^4.9|^5.8|^6.3", - "phpunit/phpunit": "^9.0" + "orchestra/testbench": "^7.0", + "pestphp/pest": "^1.20", + "spatie/laravel-ray": "^1.28" }, "type": "library", "extra": { @@ -4721,7 +4781,8 @@ }, "autoload": { "psr-4": { - "Spatie\\QueryBuilder\\": "src" + "Spatie\\QueryBuilder\\": "src", + "Spatie\\QueryBuilder\\Database\\Factories\\": "database/factories" } }, "notification-url": "https://packagist.org/downloads/", @@ -4752,29 +4813,28 @@ "type": "custom" } ], - "time": "2022-01-09T15:39:13+00:00" + "time": "2022-07-29T14:19:59+00:00" }, { "name": "staudenmeir/belongs-to-through", - "version": "v2.11.2", + "version": "v2.12.1", "source": { "type": "git", "url": "https://github.com/staudenmeir/belongs-to-through.git", - "reference": "32d03527163a3edd7f88e4b74b03575e4bdb5db7" + "reference": "8316d274db603f63b16bb1c67379b0fa73209d98" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/staudenmeir/belongs-to-through/zipball/32d03527163a3edd7f88e4b74b03575e4bdb5db7", - "reference": "32d03527163a3edd7f88e4b74b03575e4bdb5db7", + "url": "https://api.github.com/repos/staudenmeir/belongs-to-through/zipball/8316d274db603f63b16bb1c67379b0fa73209d98", + "reference": "8316d274db603f63b16bb1c67379b0fa73209d98", "shasum": "" }, "require": { - "illuminate/database": "^8.0", - "php": "^7.3|^8.0" + "illuminate/database": "^9.0", + "php": "^8.0.2" }, "require-dev": { - "phpunit/phpunit": "^9.3", - "scrutinizer/ocular": "^1.8" + "phpunit/phpunit": "^9.5" }, "type": "library", "autoload": { @@ -4799,7 +4859,7 @@ "description": "Laravel Eloquent BelongsToThrough relationships", "support": { "issues": "https://github.com/staudenmeir/belongs-to-through/issues", - "source": "https://github.com/staudenmeir/belongs-to-through/tree/v2.11.2" + "source": "https://github.com/staudenmeir/belongs-to-through/tree/v2.12.1" }, "funding": [ { @@ -4807,46 +4867,30 @@ "type": "custom" } ], - "time": "2021-08-19T18:23:06+00:00" + "time": "2022-03-10T21:14:19+00:00" }, { - "name": "swiftmailer/swiftmailer", - "version": "v6.3.0", + "name": "stella-maris/clock", + "version": "0.1.6", "source": { "type": "git", - "url": "https://github.com/swiftmailer/swiftmailer.git", - "reference": "8a5d5072dca8f48460fce2f4131fcc495eec654c" + "url": "https://github.com/stella-maris-solutions/clock.git", + "reference": "a94228dac03c9a8411198ce8c8dacbbe99c930c3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/8a5d5072dca8f48460fce2f4131fcc495eec654c", - "reference": "8a5d5072dca8f48460fce2f4131fcc495eec654c", + "url": "https://api.github.com/repos/stella-maris-solutions/clock/zipball/a94228dac03c9a8411198ce8c8dacbbe99c930c3", + "reference": "a94228dac03c9a8411198ce8c8dacbbe99c930c3", "shasum": "" }, "require": { - "egulias/email-validator": "^2.0|^3.1", - "php": ">=7.0.0", - "symfony/polyfill-iconv": "^1.0", - "symfony/polyfill-intl-idn": "^1.10", - "symfony/polyfill-mbstring": "^1.0" - }, - "require-dev": { - "mockery/mockery": "^1.0", - "symfony/phpunit-bridge": "^4.4|^5.4" - }, - "suggest": { - "ext-intl": "Needed to support internationalized email addresses" + "php": "^7.0|^8.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "6.2-dev" - } - }, "autoload": { - "files": [ - "lib/swift_required.php" - ] + "psr-4": { + "StellaMaris\\Clock\\": "src" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -4854,79 +4898,62 @@ ], "authors": [ { - "name": "Chris Corbyn" - }, - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Andreas Heigl", + "role": "Maintainer" } ], - "description": "Swiftmailer, free feature-rich PHP mailer", - "homepage": "https://swiftmailer.symfony.com", + "description": "A pre-release of the proposed PSR-20 Clock-Interface", + "homepage": "https://gitlab.com/stella-maris/clock", "keywords": [ - "email", - "mail", - "mailer" + "clock", + "datetime", + "point in time", + "psr20" ], "support": { - "issues": "https://github.com/swiftmailer/swiftmailer/issues", - "source": "https://github.com/swiftmailer/swiftmailer/tree/v6.3.0" + "issues": "https://github.com/stella-maris-solutions/clock/issues", + "source": "https://github.com/stella-maris-solutions/clock/tree/0.1.6" }, - "funding": [ - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/swiftmailer/swiftmailer", - "type": "tidelift" - } - ], - "abandoned": "symfony/mailer", - "time": "2021-10-18T15:26:12+00:00" + "time": "2022-09-27T15:03:11+00:00" }, { "name": "symfony/console", - "version": "v5.4.9", + "version": "v6.0.13", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "829d5d1bf60b2efeb0887b7436873becc71a45eb" + "reference": "8f14753b865651c2aad107ef97475740a9b0730f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/829d5d1bf60b2efeb0887b7436873becc71a45eb", - "reference": "829d5d1bf60b2efeb0887b7436873becc71a45eb", + "url": "https://api.github.com/repos/symfony/console/zipball/8f14753b865651c2aad107ef97475740a9b0730f", + "reference": "8f14753b865651c2aad107ef97475740a9b0730f", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", + "php": ">=8.0.2", "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php73": "^1.9", - "symfony/polyfill-php80": "^1.16", "symfony/service-contracts": "^1.1|^2|^3", - "symfony/string": "^5.1|^6.0" + "symfony/string": "^5.4|^6.0" }, "conflict": { - "psr/log": ">=3", - "symfony/dependency-injection": "<4.4", - "symfony/dotenv": "<5.1", - "symfony/event-dispatcher": "<4.4", - "symfony/lock": "<4.4", - "symfony/process": "<4.4" + "symfony/dependency-injection": "<5.4", + "symfony/dotenv": "<5.4", + "symfony/event-dispatcher": "<5.4", + "symfony/lock": "<5.4", + "symfony/process": "<5.4" }, "provide": { - "psr/log-implementation": "1.0|2.0" + "psr/log-implementation": "1.0|2.0|3.0" }, "require-dev": { - "psr/log": "^1|^2", - "symfony/config": "^4.4|^5.0|^6.0", - "symfony/dependency-injection": "^4.4|^5.0|^6.0", - "symfony/event-dispatcher": "^4.4|^5.0|^6.0", - "symfony/lock": "^4.4|^5.0|^6.0", - "symfony/process": "^4.4|^5.0|^6.0", - "symfony/var-dumper": "^4.4|^5.0|^6.0" + "psr/log": "^1|^2|^3", + "symfony/config": "^5.4|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/event-dispatcher": "^5.4|^6.0", + "symfony/lock": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/var-dumper": "^5.4|^6.0" }, "suggest": { "psr/log": "For using the console logger", @@ -4966,7 +4993,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.4.9" + "source": "https://github.com/symfony/console/tree/v6.0.13" }, "funding": [ { @@ -4982,25 +5009,24 @@ "type": "tidelift" } ], - "time": "2022-05-18T06:17:34+00:00" + "time": "2022-09-03T14:23:25+00:00" }, { "name": "symfony/css-selector", - "version": "v5.4.3", + "version": "v6.0.11", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "b0a190285cd95cb019237851205b8140ef6e368e" + "reference": "ab2746acddc4f03a7234c8441822ac5d5c63efe9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/b0a190285cd95cb019237851205b8140ef6e368e", - "reference": "b0a190285cd95cb019237851205b8140ef6e368e", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/ab2746acddc4f03a7234c8441822ac5d5c63efe9", + "reference": "ab2746acddc4f03a7234c8441822ac5d5c63efe9", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/polyfill-php80": "^1.16" + "php": ">=8.0.2" }, "type": "library", "autoload": { @@ -5032,7 +5058,7 @@ "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/css-selector/tree/v5.4.3" + "source": "https://github.com/symfony/css-selector/tree/v6.0.11" }, "funding": [ { @@ -5048,29 +5074,29 @@ "type": "tidelift" } ], - "time": "2022-01-02T09:53:40+00:00" + "time": "2022-06-27T17:10:44+00:00" }, { "name": "symfony/deprecation-contracts", - "version": "v2.5.1", + "version": "v3.0.2", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66" + "reference": "26954b3d62a6c5fd0ea8a2a00c0353a14978d05c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/e8b495ea28c1d97b5e0c121748d6f9b53d075c66", - "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/26954b3d62a6c5fd0ea8a2a00c0353a14978d05c", + "reference": "26954b3d62a6c5fd0ea8a2a00c0353a14978d05c", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=8.0.2" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "2.5-dev" + "dev-main": "3.0-dev" }, "thanks": { "name": "symfony/contracts", @@ -5099,7 +5125,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.1" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.0.2" }, "funding": [ { @@ -5115,31 +5141,31 @@ "type": "tidelift" } ], - "time": "2022-01-02T09:53:40+00:00" + "time": "2022-01-02T09:55:41+00:00" }, { "name": "symfony/error-handler", - "version": "v5.4.9", + "version": "v6.0.11", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "c116cda1f51c678782768dce89a45f13c949455d" + "reference": "cb302377e1b862540436f22be9ff07079a5836ae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/c116cda1f51c678782768dce89a45f13c949455d", - "reference": "c116cda1f51c678782768dce89a45f13c949455d", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/cb302377e1b862540436f22be9ff07079a5836ae", + "reference": "cb302377e1b862540436f22be9ff07079a5836ae", "shasum": "" }, "require": { - "php": ">=7.2.5", + "php": ">=8.0.2", "psr/log": "^1|^2|^3", - "symfony/var-dumper": "^4.4|^5.0|^6.0" + "symfony/var-dumper": "^5.4|^6.0" }, "require-dev": { "symfony/deprecation-contracts": "^2.1|^3", - "symfony/http-kernel": "^4.4|^5.0|^6.0", - "symfony/serializer": "^4.4|^5.0|^6.0" + "symfony/http-kernel": "^5.4|^6.0", + "symfony/serializer": "^5.4|^6.0" }, "bin": [ "Resources/bin/patch-type-declarations" @@ -5170,7 +5196,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v5.4.9" + "source": "https://github.com/symfony/error-handler/tree/v6.0.11" }, "funding": [ { @@ -5186,44 +5212,42 @@ "type": "tidelift" } ], - "time": "2022-05-21T13:57:48+00:00" + "time": "2022-07-29T07:39:48+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v5.4.9", + "version": "v6.0.9", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "8e6ce1cc0279e3ff3c8ff0f43813bc88d21ca1bc" + "reference": "5c85b58422865d42c6eb46f7693339056db098a8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/8e6ce1cc0279e3ff3c8ff0f43813bc88d21ca1bc", - "reference": "8e6ce1cc0279e3ff3c8ff0f43813bc88d21ca1bc", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/5c85b58422865d42c6eb46f7693339056db098a8", + "reference": "5c85b58422865d42c6eb46f7693339056db098a8", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/event-dispatcher-contracts": "^2|^3", - "symfony/polyfill-php80": "^1.16" + "php": ">=8.0.2", + "symfony/event-dispatcher-contracts": "^2|^3" }, "conflict": { - "symfony/dependency-injection": "<4.4" + "symfony/dependency-injection": "<5.4" }, "provide": { "psr/event-dispatcher-implementation": "1.0", - "symfony/event-dispatcher-implementation": "2.0" + "symfony/event-dispatcher-implementation": "2.0|3.0" }, "require-dev": { "psr/log": "^1|^2|^3", - "symfony/config": "^4.4|^5.0|^6.0", - "symfony/dependency-injection": "^4.4|^5.0|^6.0", - "symfony/error-handler": "^4.4|^5.0|^6.0", - "symfony/expression-language": "^4.4|^5.0|^6.0", - "symfony/http-foundation": "^4.4|^5.0|^6.0", + "symfony/config": "^5.4|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/error-handler": "^5.4|^6.0", + "symfony/expression-language": "^5.4|^6.0", + "symfony/http-foundation": "^5.4|^6.0", "symfony/service-contracts": "^1.1|^2|^3", - "symfony/stopwatch": "^4.4|^5.0|^6.0" + "symfony/stopwatch": "^5.4|^6.0" }, "suggest": { "symfony/dependency-injection": "", @@ -5255,7 +5279,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v5.4.9" + "source": "https://github.com/symfony/event-dispatcher/tree/v6.0.9" }, "funding": [ { @@ -5271,24 +5295,24 @@ "type": "tidelift" } ], - "time": "2022-05-05T16:45:39+00:00" + "time": "2022-05-05T16:45:52+00:00" }, { "name": "symfony/event-dispatcher-contracts", - "version": "v2.5.1", + "version": "v3.0.2", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "f98b54df6ad059855739db6fcbc2d36995283fe1" + "reference": "7bc61cc2db649b4637d331240c5346dcc7708051" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/f98b54df6ad059855739db6fcbc2d36995283fe1", - "reference": "f98b54df6ad059855739db6fcbc2d36995283fe1", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/7bc61cc2db649b4637d331240c5346dcc7708051", + "reference": "7bc61cc2db649b4637d331240c5346dcc7708051", "shasum": "" }, "require": { - "php": ">=7.2.5", + "php": ">=8.0.2", "psr/event-dispatcher": "^1" }, "suggest": { @@ -5297,7 +5321,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "2.5-dev" + "dev-main": "3.0-dev" }, "thanks": { "name": "symfony/contracts", @@ -5334,7 +5358,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v2.5.1" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.0.2" }, "funding": [ { @@ -5350,26 +5374,24 @@ "type": "tidelift" } ], - "time": "2022-01-02T09:53:40+00:00" + "time": "2022-01-02T09:55:41+00:00" }, { "name": "symfony/finder", - "version": "v5.4.8", + "version": "v6.0.11", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "9b630f3427f3ebe7cd346c277a1408b00249dad9" + "reference": "09cb683ba5720385ea6966e5e06be2a34f2568b1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/9b630f3427f3ebe7cd346c277a1408b00249dad9", - "reference": "9b630f3427f3ebe7cd346c277a1408b00249dad9", + "url": "https://api.github.com/repos/symfony/finder/zipball/09cb683ba5720385ea6966e5e06be2a34f2568b1", + "reference": "09cb683ba5720385ea6966e5e06be2a34f2568b1", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/polyfill-php80": "^1.16" + "php": ">=8.0.2" }, "type": "library", "autoload": { @@ -5397,7 +5419,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v5.4.8" + "source": "https://github.com/symfony/finder/tree/v6.0.11" }, "funding": [ { @@ -5413,41 +5435,52 @@ "type": "tidelift" } ], - "time": "2022-04-15T08:07:45+00:00" + "time": "2022-07-29T07:39:48+00:00" }, { - "name": "symfony/http-foundation", - "version": "v5.4.9", + "name": "symfony/http-client", + "version": "v6.0.13", "source": { "type": "git", - "url": "https://github.com/symfony/http-foundation.git", - "reference": "6b0d0e4aca38d57605dcd11e2416994b38774522" + "url": "https://github.com/symfony/http-client.git", + "reference": "2067d3c398d47292f3b413fcc4f56385c1afd0d4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/6b0d0e4aca38d57605dcd11e2416994b38774522", - "reference": "6b0d0e4aca38d57605dcd11e2416994b38774522", + "url": "https://api.github.com/repos/symfony/http-client/zipball/2067d3c398d47292f3b413fcc4f56385c1afd0d4", + "reference": "2067d3c398d47292f3b413fcc4f56385c1afd0d4", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/polyfill-mbstring": "~1.1", - "symfony/polyfill-php80": "^1.16" + "php": ">=8.0.2", + "psr/log": "^1|^2|^3", + "symfony/http-client-contracts": "^3", + "symfony/service-contracts": "^1.0|^2|^3" }, - "require-dev": { - "predis/predis": "~1.0", - "symfony/cache": "^4.4|^5.0|^6.0", - "symfony/expression-language": "^4.4|^5.0|^6.0", - "symfony/mime": "^4.4|^5.0|^6.0" + "provide": { + "php-http/async-client-implementation": "*", + "php-http/client-implementation": "*", + "psr/http-client-implementation": "1.0", + "symfony/http-client-implementation": "3.0" }, - "suggest": { - "symfony/mime": "To use the file extension guesser" + "require-dev": { + "amphp/amp": "^2.5", + "amphp/http-client": "^4.2.1", + "amphp/http-tunnel": "^1.0", + "amphp/socket": "^1.1", + "guzzlehttp/promises": "^1.4", + "nyholm/psr7": "^1.0", + "php-http/httplug": "^1.0|^2.0", + "psr/http-client": "^1.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/http-kernel": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/stopwatch": "^5.4|^6.0" }, "type": "library", "autoload": { "psr-4": { - "Symfony\\Component\\HttpFoundation\\": "" + "Symfony\\Component\\HttpClient\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -5459,18 +5492,18 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Defines an object-oriented layer for the HTTP specification", + "description": "Provides powerful methods to fetch HTTP resources synchronously or asynchronously", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v5.4.9" + "source": "https://github.com/symfony/http-client/tree/v6.0.13" }, "funding": [ { @@ -5486,84 +5519,42 @@ "type": "tidelift" } ], - "time": "2022-05-17T15:07:29+00:00" + "time": "2022-09-09T09:33:56+00:00" }, { - "name": "symfony/http-kernel", - "version": "v5.4.9", + "name": "symfony/http-client-contracts", + "version": "v3.0.2", "source": { "type": "git", - "url": "https://github.com/symfony/http-kernel.git", - "reference": "34b121ad3dc761f35fe1346d2f15618f8cbf77f8" + "url": "https://github.com/symfony/http-client-contracts.git", + "reference": "4184b9b63af1edaf35b6a7974c6f1f9f33294129" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/34b121ad3dc761f35fe1346d2f15618f8cbf77f8", - "reference": "34b121ad3dc761f35fe1346d2f15618f8cbf77f8", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/4184b9b63af1edaf35b6a7974c6f1f9f33294129", + "reference": "4184b9b63af1edaf35b6a7974c6f1f9f33294129", "shasum": "" }, "require": { - "php": ">=7.2.5", - "psr/log": "^1|^2", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/error-handler": "^4.4|^5.0|^6.0", - "symfony/event-dispatcher": "^5.0|^6.0", - "symfony/http-foundation": "^5.3.7|^6.0", - "symfony/polyfill-ctype": "^1.8", - "symfony/polyfill-php73": "^1.9", - "symfony/polyfill-php80": "^1.16" - }, - "conflict": { - "symfony/browser-kit": "<5.4", - "symfony/cache": "<5.0", - "symfony/config": "<5.0", - "symfony/console": "<4.4", - "symfony/dependency-injection": "<5.3", - "symfony/doctrine-bridge": "<5.0", - "symfony/form": "<5.0", - "symfony/http-client": "<5.0", - "symfony/mailer": "<5.0", - "symfony/messenger": "<5.0", - "symfony/translation": "<5.0", - "symfony/twig-bridge": "<5.0", - "symfony/validator": "<5.0", - "twig/twig": "<2.13" - }, - "provide": { - "psr/log-implementation": "1.0|2.0" - }, - "require-dev": { - "psr/cache": "^1.0|^2.0|^3.0", - "symfony/browser-kit": "^5.4|^6.0", - "symfony/config": "^5.0|^6.0", - "symfony/console": "^4.4|^5.0|^6.0", - "symfony/css-selector": "^4.4|^5.0|^6.0", - "symfony/dependency-injection": "^5.3|^6.0", - "symfony/dom-crawler": "^4.4|^5.0|^6.0", - "symfony/expression-language": "^4.4|^5.0|^6.0", - "symfony/finder": "^4.4|^5.0|^6.0", - "symfony/http-client-contracts": "^1.1|^2|^3", - "symfony/process": "^4.4|^5.0|^6.0", - "symfony/routing": "^4.4|^5.0|^6.0", - "symfony/stopwatch": "^4.4|^5.0|^6.0", - "symfony/translation": "^4.4|^5.0|^6.0", - "symfony/translation-contracts": "^1.1|^2|^3", - "twig/twig": "^2.13|^3.0.4" + "php": ">=8.0.2" }, "suggest": { - "symfony/browser-kit": "", - "symfony/config": "", - "symfony/console": "", - "symfony/dependency-injection": "" + "symfony/http-client-implementation": "" }, "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, "autoload": { "psr-4": { - "Symfony\\Component\\HttpKernel\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] + "Symfony\\Contracts\\HttpClient\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -5571,18 +5562,26 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Provides a structured process for converting a Request into a Response", + "description": "Generic abstractions related to HTTP clients", "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], "support": { - "source": "https://github.com/symfony/http-kernel/tree/v5.4.9" + "source": "https://github.com/symfony/http-client-contracts/tree/v3.0.2" }, "funding": [ { @@ -5598,47 +5597,43 @@ "type": "tidelift" } ], - "time": "2022-05-27T07:09:08+00:00" + "time": "2022-04-12T16:11:42+00:00" }, { - "name": "symfony/mime", - "version": "v5.4.9", + "name": "symfony/http-foundation", + "version": "v6.0.13", "source": { "type": "git", - "url": "https://github.com/symfony/mime.git", - "reference": "2b3802a24e48d0cfccf885173d2aac91e73df92e" + "url": "https://github.com/symfony/http-foundation.git", + "reference": "294208f37a73b7ae64b4297d936e890d5b514902" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/2b3802a24e48d0cfccf885173d2aac91e73df92e", - "reference": "2b3802a24e48d0cfccf885173d2aac91e73df92e", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/294208f37a73b7ae64b4297d936e890d5b514902", + "reference": "294208f37a73b7ae64b4297d936e890d5b514902", "shasum": "" }, "require": { - "php": ">=7.2.5", + "php": ">=8.0.2", "symfony/deprecation-contracts": "^2.1|^3", - "symfony/polyfill-intl-idn": "^1.10", - "symfony/polyfill-mbstring": "^1.0", - "symfony/polyfill-php80": "^1.16" - }, - "conflict": { - "egulias/email-validator": "~3.0.0", - "phpdocumentor/reflection-docblock": "<3.2.2", - "phpdocumentor/type-resolver": "<1.4.0", - "symfony/mailer": "<4.4" + "symfony/polyfill-mbstring": "~1.1" }, "require-dev": { - "egulias/email-validator": "^2.1.10|^3.1", - "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", - "symfony/dependency-injection": "^4.4|^5.0|^6.0", - "symfony/property-access": "^4.4|^5.1|^6.0", - "symfony/property-info": "^4.4|^5.1|^6.0", - "symfony/serializer": "^5.2|^6.0" + "predis/predis": "~1.0", + "symfony/cache": "^5.4|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/expression-language": "^5.4|^6.0", + "symfony/http-kernel": "^5.4.12|^6.0.12|^6.1.4", + "symfony/mime": "^5.4|^6.0", + "symfony/rate-limiter": "^5.2|^6.0" + }, + "suggest": { + "symfony/mime": "To use the file extension guesser" }, "type": "library", "autoload": { "psr-4": { - "Symfony\\Component\\Mime\\": "" + "Symfony\\Component\\HttpFoundation\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -5658,14 +5653,10 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Allows manipulating MIME messages", + "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", - "keywords": [ - "mime", - "mime-type" - ], "support": { - "source": "https://github.com/symfony/mime/tree/v5.4.9" + "source": "https://github.com/symfony/http-foundation/tree/v6.0.13" }, "funding": [ { @@ -5681,48 +5672,81 @@ "type": "tidelift" } ], - "time": "2022-05-21T10:24:18+00:00" + "time": "2022-09-17T07:33:45+00:00" }, { - "name": "symfony/polyfill-ctype", - "version": "v1.25.0", + "name": "symfony/http-kernel", + "version": "v6.0.13", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "30885182c981ab175d4d034db0f6f469898070ab" + "url": "https://github.com/symfony/http-kernel.git", + "reference": "5939a039103580d8d86a4c80e245258ad50c91b2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/30885182c981ab175d4d034db0f6f469898070ab", - "reference": "30885182c981ab175d4d034db0f6f469898070ab", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/5939a039103580d8d86a4c80e245258ad50c91b2", + "reference": "5939a039103580d8d86a4c80e245258ad50c91b2", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=8.0.2", + "psr/log": "^1|^2|^3", + "symfony/error-handler": "^5.4|^6.0", + "symfony/event-dispatcher": "^5.4|^6.0", + "symfony/http-foundation": "^5.4|^6.0", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "symfony/browser-kit": "<5.4", + "symfony/cache": "<5.4", + "symfony/config": "<5.4", + "symfony/console": "<5.4", + "symfony/dependency-injection": "<5.4", + "symfony/doctrine-bridge": "<5.4", + "symfony/form": "<5.4", + "symfony/http-client": "<5.4", + "symfony/mailer": "<5.4", + "symfony/messenger": "<5.4", + "symfony/translation": "<5.4", + "symfony/twig-bridge": "<5.4", + "symfony/validator": "<5.4", + "twig/twig": "<2.13" }, "provide": { - "ext-ctype": "*" + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/cache": "^1.0|^2.0|^3.0", + "symfony/browser-kit": "^5.4|^6.0", + "symfony/config": "^5.4|^6.0", + "symfony/console": "^5.4|^6.0", + "symfony/css-selector": "^5.4|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/dom-crawler": "^5.4|^6.0", + "symfony/expression-language": "^5.4|^6.0", + "symfony/finder": "^5.4|^6.0", + "symfony/http-client-contracts": "^1.1|^2|^3", + "symfony/process": "^5.4|^6.0", + "symfony/routing": "^5.4|^6.0", + "symfony/stopwatch": "^5.4|^6.0", + "symfony/translation": "^5.4|^6.0", + "symfony/translation-contracts": "^1.1|^2|^3", + "twig/twig": "^2.13|^3.0.4" }, "suggest": { - "ext-ctype": "For best performance" + "symfony/browser-kit": "", + "symfony/config": "", + "symfony/console": "", + "symfony/dependency-injection": "" }, "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, "autoload": { - "files": [ - "bootstrap.php" - ], "psr-4": { - "Symfony\\Polyfill\\Ctype\\": "" - } + "Symfony\\Component\\HttpKernel\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -5730,24 +5754,18 @@ ], "authors": [ { - "name": "Gert de Pagter", - "email": "BackEndTea@gmail.com" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for ctype functions", + "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "ctype", - "polyfill", - "portable" - ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.25.0" + "source": "https://github.com/symfony/http-kernel/tree/v6.0.13" }, "funding": [ { @@ -5763,35 +5781,255 @@ "type": "tidelift" } ], - "time": "2021-10-20T20:35:02+00:00" + "time": "2022-09-30T08:03:37+00:00" }, { - "name": "symfony/polyfill-iconv", - "version": "v1.25.0", + "name": "symfony/mailer", + "version": "v6.0.13", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-iconv.git", - "reference": "f1aed619e28cb077fc83fac8c4c0383578356e40" + "url": "https://github.com/symfony/mailer.git", + "reference": "6269c872ab4792e8facbf8af27a2fbee8429f217" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/f1aed619e28cb077fc83fac8c4c0383578356e40", - "reference": "f1aed619e28cb077fc83fac8c4c0383578356e40", + "url": "https://api.github.com/repos/symfony/mailer/zipball/6269c872ab4792e8facbf8af27a2fbee8429f217", + "reference": "6269c872ab4792e8facbf8af27a2fbee8429f217", "shasum": "" }, "require": { - "php": ">=7.1" + "egulias/email-validator": "^2.1.10|^3", + "php": ">=8.0.2", + "psr/event-dispatcher": "^1", + "psr/log": "^1|^2|^3", + "symfony/event-dispatcher": "^5.4|^6.0", + "symfony/mime": "^5.4|^6.0", + "symfony/service-contracts": "^1.1|^2|^3" }, - "provide": { - "ext-iconv": "*" + "conflict": { + "symfony/http-kernel": "<5.4" }, - "suggest": { - "ext-iconv": "For best performance" + "require-dev": { + "symfony/http-client-contracts": "^1.1|^2|^3", + "symfony/messenger": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Mailer\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Helps sending emails", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/mailer/tree/v6.0.13" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-08-29T06:49:22+00:00" + }, + { + "name": "symfony/mailgun-mailer", + "version": "v6.0.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/mailgun-mailer.git", + "reference": "f0d032c26683b26f4bc26864e09b1e08fa55226e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/mailgun-mailer/zipball/f0d032c26683b26f4bc26864e09b1e08fa55226e", + "reference": "f0d032c26683b26f4bc26864e09b1e08fa55226e", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/mailer": "^5.4|^6.0" + }, + "require-dev": { + "symfony/http-client": "^5.4|^6.0" + }, + "type": "symfony-mailer-bridge", + "autoload": { + "psr-4": { + "Symfony\\Component\\Mailer\\Bridge\\Mailgun\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Mailgun Mailer Bridge", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/mailgun-mailer/tree/v6.0.7" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-03-24T17:11:42+00:00" + }, + { + "name": "symfony/mime", + "version": "v6.0.13", + "source": { + "type": "git", + "url": "https://github.com/symfony/mime.git", + "reference": "c1d6eba531d956c23b3127dc6ae6f5ac4a90db6c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/mime/zipball/c1d6eba531d956c23b3127dc6ae6f5ac4a90db6c", + "reference": "c1d6eba531d956c23b3127dc6ae6f5ac4a90db6c", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/polyfill-intl-idn": "^1.10", + "symfony/polyfill-mbstring": "^1.0" + }, + "conflict": { + "egulias/email-validator": "~3.0.0", + "phpdocumentor/reflection-docblock": "<3.2.2", + "phpdocumentor/type-resolver": "<1.4.0", + "symfony/mailer": "<5.4" + }, + "require-dev": { + "egulias/email-validator": "^2.1.10|^3.1", + "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/property-access": "^5.4|^6.0", + "symfony/property-info": "^5.4|^6.0", + "symfony/serializer": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Mime\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Allows manipulating MIME messages", + "homepage": "https://symfony.com", + "keywords": [ + "mime", + "mime-type" + ], + "support": { + "source": "https://github.com/symfony/mime/tree/v6.0.13" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-09-02T08:05:03+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.26.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4", + "reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "1.23-dev" + "dev-main": "1.26-dev" }, "thanks": { "name": "symfony/polyfill", @@ -5803,7 +6041,7 @@ "bootstrap.php" ], "psr-4": { - "Symfony\\Polyfill\\Iconv\\": "" + "Symfony\\Polyfill\\Ctype\\": "" } }, "notification-url": "https://packagist.org/downloads/", @@ -5812,25 +6050,24 @@ ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for the Iconv extension", + "description": "Symfony polyfill for ctype functions", "homepage": "https://symfony.com", "keywords": [ "compatibility", - "iconv", + "ctype", "polyfill", - "portable", - "shim" + "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-iconv/tree/v1.25.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.26.0" }, "funding": [ { @@ -5846,20 +6083,20 @@ "type": "tidelift" } ], - "time": "2022-01-04T09:04:05+00:00" + "time": "2022-05-24T11:49:31+00:00" }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.25.0", + "version": "v1.26.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "81b86b50cf841a64252b439e738e97f4a34e2783" + "reference": "433d05519ce6990bf3530fba6957499d327395c2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/81b86b50cf841a64252b439e738e97f4a34e2783", - "reference": "81b86b50cf841a64252b439e738e97f4a34e2783", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/433d05519ce6990bf3530fba6957499d327395c2", + "reference": "433d05519ce6990bf3530fba6957499d327395c2", "shasum": "" }, "require": { @@ -5871,7 +6108,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.23-dev" + "dev-main": "1.26-dev" }, "thanks": { "name": "symfony/polyfill", @@ -5911,7 +6148,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.25.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.26.0" }, "funding": [ { @@ -5927,20 +6164,20 @@ "type": "tidelift" } ], - "time": "2021-11-23T21:10:46+00:00" + "time": "2022-05-24T11:49:31+00:00" }, { "name": "symfony/polyfill-intl-idn", - "version": "v1.25.0", + "version": "v1.26.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-idn.git", - "reference": "749045c69efb97c70d25d7463abba812e91f3a44" + "reference": "59a8d271f00dd0e4c2e518104cc7963f655a1aa8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/749045c69efb97c70d25d7463abba812e91f3a44", - "reference": "749045c69efb97c70d25d7463abba812e91f3a44", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/59a8d271f00dd0e4c2e518104cc7963f655a1aa8", + "reference": "59a8d271f00dd0e4c2e518104cc7963f655a1aa8", "shasum": "" }, "require": { @@ -5954,7 +6191,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.23-dev" + "dev-main": "1.26-dev" }, "thanks": { "name": "symfony/polyfill", @@ -5998,7 +6235,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.25.0" + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.26.0" }, "funding": [ { @@ -6014,20 +6251,20 @@ "type": "tidelift" } ], - "time": "2021-09-14T14:02:44+00:00" + "time": "2022-05-24T11:49:31+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.25.0", + "version": "v1.26.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8" + "reference": "219aa369ceff116e673852dce47c3a41794c14bd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/8590a5f561694770bdcd3f9b5c69dde6945028e8", - "reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/219aa369ceff116e673852dce47c3a41794c14bd", + "reference": "219aa369ceff116e673852dce47c3a41794c14bd", "shasum": "" }, "require": { @@ -6039,7 +6276,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.23-dev" + "dev-main": "1.26-dev" }, "thanks": { "name": "symfony/polyfill", @@ -6082,7 +6319,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.25.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.26.0" }, "funding": [ { @@ -6098,20 +6335,20 @@ "type": "tidelift" } ], - "time": "2021-02-19T12:13:01+00:00" + "time": "2022-05-24T11:49:31+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.25.0", + "version": "v1.26.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" + "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e", + "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e", "shasum": "" }, "require": { @@ -6126,7 +6363,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.23-dev" + "dev-main": "1.26-dev" }, "thanks": { "name": "symfony/polyfill", @@ -6165,7 +6402,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.26.0" }, "funding": [ { @@ -6181,7 +6418,7 @@ "type": "tidelift" } ], - "time": "2021-11-30T18:21:41+00:00" + "time": "2022-05-24T11:49:31+00:00" }, { "name": "symfony/polyfill-php56", @@ -6253,16 +6490,16 @@ }, { "name": "symfony/polyfill-php72", - "version": "v1.25.0", + "version": "v1.26.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "9a142215a36a3888e30d0a9eeea9766764e96976" + "reference": "bf44a9fd41feaac72b074de600314a93e2ae78e2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/9a142215a36a3888e30d0a9eeea9766764e96976", - "reference": "9a142215a36a3888e30d0a9eeea9766764e96976", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/bf44a9fd41feaac72b074de600314a93e2ae78e2", + "reference": "bf44a9fd41feaac72b074de600314a93e2ae78e2", "shasum": "" }, "require": { @@ -6271,7 +6508,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.23-dev" + "dev-main": "1.26-dev" }, "thanks": { "name": "symfony/polyfill", @@ -6309,7 +6546,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php72/tree/v1.25.0" + "source": "https://github.com/symfony/polyfill-php72/tree/v1.26.0" }, "funding": [ { @@ -6325,20 +6562,20 @@ "type": "tidelift" } ], - "time": "2021-05-27T09:17:38+00:00" + "time": "2022-05-24T11:49:31+00:00" }, { - "name": "symfony/polyfill-php73", - "version": "v1.25.0", + "name": "symfony/polyfill-php80", + "version": "v1.26.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php73.git", - "reference": "cc5db0e22b3cb4111010e48785a97f670b350ca5" + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/cc5db0e22b3cb4111010e48785a97f670b350ca5", - "reference": "cc5db0e22b3cb4111010e48785a97f670b350ca5", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/cfa0ae98841b9e461207c13ab093d76b0fa7bace", + "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace", "shasum": "" }, "require": { @@ -6347,7 +6584,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.23-dev" + "dev-main": "1.26-dev" }, "thanks": { "name": "symfony/polyfill", @@ -6359,7 +6596,7 @@ "bootstrap.php" ], "psr-4": { - "Symfony\\Polyfill\\Php73\\": "" + "Symfony\\Polyfill\\Php80\\": "" }, "classmap": [ "Resources/stubs" @@ -6370,6 +6607,10 @@ "MIT" ], "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, { "name": "Nicolas Grekas", "email": "p@tchwork.com" @@ -6379,7 +6620,7 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ "compatibility", @@ -6388,7 +6629,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php73/tree/v1.25.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.26.0" }, "funding": [ { @@ -6404,20 +6645,20 @@ "type": "tidelift" } ], - "time": "2021-06-05T21:20:04+00:00" + "time": "2022-05-10T07:21:04+00:00" }, { - "name": "symfony/polyfill-php80", - "version": "v1.25.0", + "name": "symfony/polyfill-php81", + "version": "v1.26.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "4407588e0d3f1f52efb65fbe92babe41f37fe50c" + "url": "https://github.com/symfony/polyfill-php81.git", + "reference": "13f6d1271c663dc5ae9fb843a8f16521db7687a1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/4407588e0d3f1f52efb65fbe92babe41f37fe50c", - "reference": "4407588e0d3f1f52efb65fbe92babe41f37fe50c", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/13f6d1271c663dc5ae9fb843a8f16521db7687a1", + "reference": "13f6d1271c663dc5ae9fb843a8f16521db7687a1", "shasum": "" }, "require": { @@ -6426,7 +6667,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.23-dev" + "dev-main": "1.26-dev" }, "thanks": { "name": "symfony/polyfill", @@ -6438,7 +6679,7 @@ "bootstrap.php" ], "psr-4": { - "Symfony\\Polyfill\\Php80\\": "" + "Symfony\\Polyfill\\Php81\\": "" }, "classmap": [ "Resources/stubs" @@ -6449,10 +6690,6 @@ "MIT" ], "authors": [ - { - "name": "Ion Bazan", - "email": "ion.bazan@gmail.com" - }, { "name": "Nicolas Grekas", "email": "p@tchwork.com" @@ -6462,7 +6699,7 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ "compatibility", @@ -6471,7 +6708,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.25.0" + "source": "https://github.com/symfony/polyfill-php81/tree/v1.26.0" }, "funding": [ { @@ -6487,29 +6724,35 @@ "type": "tidelift" } ], - "time": "2022-03-04T08:16:47+00:00" + "time": "2022-05-24T11:49:31+00:00" }, { - "name": "symfony/polyfill-php81", - "version": "v1.25.0", + "name": "symfony/polyfill-uuid", + "version": "v1.26.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php81.git", - "reference": "5de4ba2d41b15f9bd0e19b2ab9674135813ec98f" + "url": "https://github.com/symfony/polyfill-uuid.git", + "reference": "a41886c1c81dc075a09c71fe6db5b9d68c79de23" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/5de4ba2d41b15f9bd0e19b2ab9674135813ec98f", - "reference": "5de4ba2d41b15f9bd0e19b2ab9674135813ec98f", + "url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/a41886c1c81dc075a09c71fe6db5b9d68c79de23", + "reference": "a41886c1c81dc075a09c71fe6db5b9d68c79de23", "shasum": "" }, "require": { "php": ">=7.1" }, + "provide": { + "ext-uuid": "*" + }, + "suggest": { + "ext-uuid": "For best performance" + }, "type": "library", "extra": { "branch-alias": { - "dev-main": "1.23-dev" + "dev-main": "1.26-dev" }, "thanks": { "name": "symfony/polyfill", @@ -6521,11 +6764,8 @@ "bootstrap.php" ], "psr-4": { - "Symfony\\Polyfill\\Php81\\": "" - }, - "classmap": [ - "Resources/stubs" - ] + "Symfony\\Polyfill\\Uuid\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -6533,24 +6773,24 @@ ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Grégoire Pineau", + "email": "lyrixx@lyrixx.info" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", + "description": "Symfony polyfill for uuid functions", "homepage": "https://symfony.com", "keywords": [ "compatibility", "polyfill", "portable", - "shim" + "uuid" ], "support": { - "source": "https://github.com/symfony/polyfill-php81/tree/v1.25.0" + "source": "https://github.com/symfony/polyfill-uuid/tree/v1.26.0" }, "funding": [ { @@ -6566,25 +6806,89 @@ "type": "tidelift" } ], - "time": "2021-09-13T13:58:11+00:00" + "time": "2022-05-24T11:49:31+00:00" + }, + { + "name": "symfony/postmark-mailer", + "version": "v6.0.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/postmark-mailer.git", + "reference": "8405569233efb0140e55eb6236c9e55693f058ff" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/postmark-mailer/zipball/8405569233efb0140e55eb6236c9e55693f058ff", + "reference": "8405569233efb0140e55eb6236c9e55693f058ff", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/mailer": "^5.4|^6.0" + }, + "require-dev": { + "symfony/http-client": "^5.4|^6.0" + }, + "type": "symfony-mailer-bridge", + "autoload": { + "psr-4": { + "Symfony\\Component\\Mailer\\Bridge\\Postmark\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Postmark Mailer Bridge", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/postmark-mailer/tree/v6.0.7" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-03-24T17:11:42+00:00" }, { "name": "symfony/process", - "version": "v5.4.8", + "version": "v6.0.11", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "597f3fff8e3e91836bb0bd38f5718b56ddbde2f3" + "reference": "44270a08ccb664143dede554ff1c00aaa2247a43" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/597f3fff8e3e91836bb0bd38f5718b56ddbde2f3", - "reference": "597f3fff8e3e91836bb0bd38f5718b56ddbde2f3", + "url": "https://api.github.com/repos/symfony/process/zipball/44270a08ccb664143dede554ff1c00aaa2247a43", + "reference": "44270a08ccb664143dede554ff1c00aaa2247a43", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/polyfill-php80": "^1.16" + "php": ">=8.0.2" }, "type": "library", "autoload": { @@ -6612,7 +6916,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v5.4.8" + "source": "https://github.com/symfony/process/tree/v6.0.11" }, "funding": [ { @@ -6628,41 +6932,39 @@ "type": "tidelift" } ], - "time": "2022-04-08T05:07:18+00:00" + "time": "2022-06-27T17:10:44+00:00" }, { "name": "symfony/routing", - "version": "v5.4.8", + "version": "v6.0.11", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "e07817bb6244ea33ef5ad31abc4a9288bef3f2f7" + "reference": "434b64f7d3a582ec33fcf69baaf085473e67d639" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/e07817bb6244ea33ef5ad31abc4a9288bef3f2f7", - "reference": "e07817bb6244ea33ef5ad31abc4a9288bef3f2f7", + "url": "https://api.github.com/repos/symfony/routing/zipball/434b64f7d3a582ec33fcf69baaf085473e67d639", + "reference": "434b64f7d3a582ec33fcf69baaf085473e67d639", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/polyfill-php80": "^1.16" + "php": ">=8.0.2" }, "conflict": { "doctrine/annotations": "<1.12", - "symfony/config": "<5.3", - "symfony/dependency-injection": "<4.4", - "symfony/yaml": "<4.4" + "symfony/config": "<5.4", + "symfony/dependency-injection": "<5.4", + "symfony/yaml": "<5.4" }, "require-dev": { "doctrine/annotations": "^1.12", "psr/log": "^1|^2|^3", - "symfony/config": "^5.3|^6.0", - "symfony/dependency-injection": "^4.4|^5.0|^6.0", - "symfony/expression-language": "^4.4|^5.0|^6.0", - "symfony/http-foundation": "^4.4|^5.0|^6.0", - "symfony/yaml": "^4.4|^5.0|^6.0" + "symfony/config": "^5.4|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/expression-language": "^5.4|^6.0", + "symfony/http-foundation": "^5.4|^6.0", + "symfony/yaml": "^5.4|^6.0" }, "suggest": { "symfony/config": "For using the all-in-one router or any loader", @@ -6702,7 +7004,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v5.4.8" + "source": "https://github.com/symfony/routing/tree/v6.0.11" }, "funding": [ { @@ -6718,26 +7020,25 @@ "type": "tidelift" } ], - "time": "2022-04-18T21:45:37+00:00" + "time": "2022-07-20T13:45:53+00:00" }, { "name": "symfony/service-contracts", - "version": "v2.5.1", + "version": "v3.0.2", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "24d9dc654b83e91aa59f9d167b131bc3b5bea24c" + "reference": "d78d39c1599bd1188b8e26bb341da52c3c6d8a66" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/24d9dc654b83e91aa59f9d167b131bc3b5bea24c", - "reference": "24d9dc654b83e91aa59f9d167b131bc3b5bea24c", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/d78d39c1599bd1188b8e26bb341da52c3c6d8a66", + "reference": "d78d39c1599bd1188b8e26bb341da52c3c6d8a66", "shasum": "" }, "require": { - "php": ">=7.2.5", - "psr/container": "^1.1", - "symfony/deprecation-contracts": "^2.1|^3" + "php": ">=8.0.2", + "psr/container": "^2.0" }, "conflict": { "ext-psr": "<1.1|>=2" @@ -6748,7 +7049,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "2.5-dev" + "dev-main": "3.0-dev" }, "thanks": { "name": "symfony/contracts", @@ -6785,7 +7086,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v2.5.1" + "source": "https://github.com/symfony/service-contracts/tree/v3.0.2" }, "funding": [ { @@ -6801,38 +7102,37 @@ "type": "tidelift" } ], - "time": "2022-03-13T20:07:29+00:00" + "time": "2022-05-30T19:17:58+00:00" }, { "name": "symfony/string", - "version": "v5.4.9", + "version": "v6.0.13", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "985e6a9703ef5ce32ba617c9c7d97873bb7b2a99" + "reference": "65e99fb179e7241606377e4042cd2161f3dd1c05" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/985e6a9703ef5ce32ba617c9c7d97873bb7b2a99", - "reference": "985e6a9703ef5ce32ba617c9c7d97873bb7b2a99", + "url": "https://api.github.com/repos/symfony/string/zipball/65e99fb179e7241606377e4042cd2161f3dd1c05", + "reference": "65e99fb179e7241606377e4042cd2161f3dd1c05", "shasum": "" }, "require": { - "php": ">=7.2.5", + "php": ">=8.0.2", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-intl-grapheme": "~1.0", "symfony/polyfill-intl-normalizer": "~1.0", - "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php80": "~1.15" + "symfony/polyfill-mbstring": "~1.0" }, "conflict": { - "symfony/translation-contracts": ">=3.0" + "symfony/translation-contracts": "<2.0" }, "require-dev": { - "symfony/error-handler": "^4.4|^5.0|^6.0", - "symfony/http-client": "^4.4|^5.0|^6.0", - "symfony/translation-contracts": "^1.1|^2", - "symfony/var-exporter": "^4.4|^5.0|^6.0" + "symfony/error-handler": "^5.4|^6.0", + "symfony/http-client": "^5.4|^6.0", + "symfony/translation-contracts": "^2.0|^3.0", + "symfony/var-exporter": "^5.4|^6.0" }, "type": "library", "autoload": { @@ -6871,7 +7171,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v5.4.9" + "source": "https://github.com/symfony/string/tree/v6.0.13" }, "funding": [ { @@ -6887,52 +7187,50 @@ "type": "tidelift" } ], - "time": "2022-04-19T10:40:37+00:00" + "time": "2022-09-02T08:05:03+00:00" }, { "name": "symfony/translation", - "version": "v5.4.9", + "version": "v6.0.12", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "1639abc1177d26bcd4320e535e664cef067ab0ca" + "reference": "5e71973b4991e141271465dacf4bf9e719941424" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/1639abc1177d26bcd4320e535e664cef067ab0ca", - "reference": "1639abc1177d26bcd4320e535e664cef067ab0ca", + "url": "https://api.github.com/repos/symfony/translation/zipball/5e71973b4991e141271465dacf4bf9e719941424", + "reference": "5e71973b4991e141271465dacf4bf9e719941424", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", + "php": ">=8.0.2", "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php80": "^1.16", - "symfony/translation-contracts": "^2.3" + "symfony/translation-contracts": "^2.3|^3.0" }, "conflict": { - "symfony/config": "<4.4", - "symfony/console": "<5.3", - "symfony/dependency-injection": "<5.0", - "symfony/http-kernel": "<5.0", - "symfony/twig-bundle": "<5.0", - "symfony/yaml": "<4.4" + "symfony/config": "<5.4", + "symfony/console": "<5.4", + "symfony/dependency-injection": "<5.4", + "symfony/http-kernel": "<5.4", + "symfony/twig-bundle": "<5.4", + "symfony/yaml": "<5.4" }, "provide": { - "symfony/translation-implementation": "2.3" + "symfony/translation-implementation": "2.3|3.0" }, "require-dev": { "psr/log": "^1|^2|^3", - "symfony/config": "^4.4|^5.0|^6.0", + "symfony/config": "^5.4|^6.0", "symfony/console": "^5.4|^6.0", - "symfony/dependency-injection": "^5.0|^6.0", - "symfony/finder": "^4.4|^5.0|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/finder": "^5.4|^6.0", "symfony/http-client-contracts": "^1.1|^2.0|^3.0", - "symfony/http-kernel": "^5.0|^6.0", - "symfony/intl": "^4.4|^5.0|^6.0", + "symfony/http-kernel": "^5.4|^6.0", + "symfony/intl": "^5.4|^6.0", "symfony/polyfill-intl-icu": "^1.21", "symfony/service-contracts": "^1.1.2|^2|^3", - "symfony/yaml": "^4.4|^5.0|^6.0" + "symfony/yaml": "^5.4|^6.0" }, "suggest": { "psr/log-implementation": "To use logging capability in translator", @@ -6968,7 +7266,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v5.4.9" + "source": "https://github.com/symfony/translation/tree/v6.0.12" }, "funding": [ { @@ -6984,24 +7282,24 @@ "type": "tidelift" } ], - "time": "2022-05-06T12:33:37+00:00" + "time": "2022-08-02T16:01:06+00:00" }, { "name": "symfony/translation-contracts", - "version": "v2.5.1", + "version": "v3.0.2", "source": { "type": "git", "url": "https://github.com/symfony/translation-contracts.git", - "reference": "1211df0afa701e45a04253110e959d4af4ef0f07" + "reference": "acbfbb274e730e5a0236f619b6168d9dedb3e282" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/1211df0afa701e45a04253110e959d4af4ef0f07", - "reference": "1211df0afa701e45a04253110e959d4af4ef0f07", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/acbfbb274e730e5a0236f619b6168d9dedb3e282", + "reference": "acbfbb274e730e5a0236f619b6168d9dedb3e282", "shasum": "" }, "require": { - "php": ">=7.2.5" + "php": ">=8.0.2" }, "suggest": { "symfony/translation-implementation": "" @@ -7009,7 +7307,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "2.5-dev" + "dev-main": "3.0-dev" }, "thanks": { "name": "symfony/contracts", @@ -7046,7 +7344,81 @@ "standards" ], "support": { - "source": "https://github.com/symfony/translation-contracts/tree/v2.5.1" + "source": "https://github.com/symfony/translation-contracts/tree/v3.0.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-06-27T17:10:44+00:00" + }, + { + "name": "symfony/uid", + "version": "v6.0.13", + "source": { + "type": "git", + "url": "https://github.com/symfony/uid.git", + "reference": "db426b27173f5e2d8b960dd10fa8ce19ea9ca5f3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/uid/zipball/db426b27173f5e2d8b960dd10fa8ce19ea9ca5f3", + "reference": "db426b27173f5e2d8b960dd10fa8ce19ea9ca5f3", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/polyfill-uuid": "^1.15" + }, + "require-dev": { + "symfony/console": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Uid\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Grégoire Pineau", + "email": "lyrixx@lyrixx.info" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to generate and represent UIDs", + "homepage": "https://symfony.com", + "keywords": [ + "UID", + "ulid", + "uuid" + ], + "support": { + "source": "https://github.com/symfony/uid/tree/v6.0.13" }, "funding": [ { @@ -7062,36 +7434,35 @@ "type": "tidelift" } ], - "time": "2022-01-02T09:53:40+00:00" + "time": "2022-09-09T09:33:56+00:00" }, { "name": "symfony/var-dumper", - "version": "v5.4.9", + "version": "v6.0.13", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "af52239a330fafd192c773795520dc2dd62b5657" + "reference": "367522dc769072f2abe554013e073eb079593829" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/af52239a330fafd192c773795520dc2dd62b5657", - "reference": "af52239a330fafd192c773795520dc2dd62b5657", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/367522dc769072f2abe554013e073eb079593829", + "reference": "367522dc769072f2abe554013e073eb079593829", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php80": "^1.16" + "php": ">=8.0.2", + "symfony/polyfill-mbstring": "~1.0" }, "conflict": { "phpunit/phpunit": "<5.4.3", - "symfony/console": "<4.4" + "symfony/console": "<5.4" }, "require-dev": { "ext-iconv": "*", - "symfony/console": "^4.4|^5.0|^6.0", - "symfony/process": "^4.4|^5.0|^6.0", - "symfony/uid": "^5.1|^6.0", + "symfony/console": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/uid": "^5.4|^6.0", "twig/twig": "^2.13|^3.0.4" }, "suggest": { @@ -7135,7 +7506,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v5.4.9" + "source": "https://github.com/symfony/var-dumper/tree/v6.0.13" }, "funding": [ { @@ -7151,35 +7522,39 @@ "type": "tidelift" } ], - "time": "2022-05-21T10:24:18+00:00" + "time": "2022-09-08T09:32:44+00:00" }, { "name": "symfony/yaml", - "version": "v4.4.37", + "version": "v5.4.12", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "d7f637cc0f0cc14beb0984f2bb50da560b271311" + "reference": "7a3aa21ac8ab1a96cc6de5bbcab4bc9fc943b18c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/d7f637cc0f0cc14beb0984f2bb50da560b271311", - "reference": "d7f637cc0f0cc14beb0984f2bb50da560b271311", + "url": "https://api.github.com/repos/symfony/yaml/zipball/7a3aa21ac8ab1a96cc6de5bbcab4bc9fc943b18c", + "reference": "7a3aa21ac8ab1a96cc6de5bbcab4bc9fc943b18c", "shasum": "" }, "require": { - "php": ">=7.1.3", - "symfony/polyfill-ctype": "~1.8" + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-ctype": "^1.8" }, "conflict": { - "symfony/console": "<3.4" + "symfony/console": "<5.3" }, "require-dev": { - "symfony/console": "^3.4|^4.0|^5.0" + "symfony/console": "^5.3|^6.0" }, "suggest": { "symfony/console": "For validating YAML files using the lint command" }, + "bin": [ + "Resources/bin/yaml-lint" + ], "type": "library", "autoload": { "psr-4": { @@ -7206,7 +7581,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v4.4.37" + "source": "https://github.com/symfony/yaml/tree/v5.4.12" }, "funding": [ { @@ -7222,20 +7597,20 @@ "type": "tidelift" } ], - "time": "2022-01-24T20:11:01+00:00" + "time": "2022-08-02T15:52:22+00:00" }, { "name": "tijsverkoyen/css-to-inline-styles", - "version": "2.2.4", + "version": "2.2.5", "source": { "type": "git", "url": "https://github.com/tijsverkoyen/CssToInlineStyles.git", - "reference": "da444caae6aca7a19c0c140f68c6182e337d5b1c" + "reference": "4348a3a06651827a27d989ad1d13efec6bb49b19" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/tijsverkoyen/CssToInlineStyles/zipball/da444caae6aca7a19c0c140f68c6182e337d5b1c", - "reference": "da444caae6aca7a19c0c140f68c6182e337d5b1c", + "url": "https://api.github.com/repos/tijsverkoyen/CssToInlineStyles/zipball/4348a3a06651827a27d989ad1d13efec6bb49b19", + "reference": "4348a3a06651827a27d989ad1d13efec6bb49b19", "shasum": "" }, "require": { @@ -7273,9 +7648,9 @@ "homepage": "https://github.com/tijsverkoyen/CssToInlineStyles", "support": { "issues": "https://github.com/tijsverkoyen/CssToInlineStyles/issues", - "source": "https://github.com/tijsverkoyen/CssToInlineStyles/tree/2.2.4" + "source": "https://github.com/tijsverkoyen/CssToInlineStyles/tree/2.2.5" }, - "time": "2021-12-08T09:12:39+00:00" + "time": "2022-09-12T13:28:28+00:00" }, { "name": "vlucas/phpdotenv", @@ -7359,16 +7734,16 @@ }, { "name": "voku/portable-ascii", - "version": "1.6.1", + "version": "2.0.1", "source": { "type": "git", "url": "https://github.com/voku/portable-ascii.git", - "reference": "87337c91b9dfacee02452244ee14ab3c43bc485a" + "reference": "b56450eed252f6801410d810c8e1727224ae0743" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/voku/portable-ascii/zipball/87337c91b9dfacee02452244ee14ab3c43bc485a", - "reference": "87337c91b9dfacee02452244ee14ab3c43bc485a", + "url": "https://api.github.com/repos/voku/portable-ascii/zipball/b56450eed252f6801410d810c8e1727224ae0743", + "reference": "b56450eed252f6801410d810c8e1727224ae0743", "shasum": "" }, "require": { @@ -7405,7 +7780,7 @@ ], "support": { "issues": "https://github.com/voku/portable-ascii/issues", - "source": "https://github.com/voku/portable-ascii/tree/1.6.1" + "source": "https://github.com/voku/portable-ascii/tree/2.0.1" }, "funding": [ { @@ -7429,25 +7804,25 @@ "type": "tidelift" } ], - "time": "2022-01-24T18:55:24+00:00" + "time": "2022-03-08T17:03:00+00:00" }, { "name": "webmozart/assert", - "version": "1.10.0", + "version": "1.11.0", "source": { "type": "git", "url": "https://github.com/webmozarts/assert.git", - "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25" + "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozarts/assert/zipball/6964c76c7804814a842473e0c8fd15bab0f18e25", - "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/11cb2199493b2f8a3b53e7f19068fc6aac760991", + "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0", - "symfony/polyfill-ctype": "^1.8" + "ext-ctype": "*", + "php": "^7.2 || ^8.0" }, "conflict": { "phpstan/phpstan": "<0.12.20", @@ -7485,9 +7860,9 @@ ], "support": { "issues": "https://github.com/webmozarts/assert/issues", - "source": "https://github.com/webmozarts/assert/tree/1.10.0" + "source": "https://github.com/webmozarts/assert/tree/1.11.0" }, - "time": "2021-03-09T10:59:23+00:00" + "time": "2022-06-03T18:03:27+00:00" } ], "packages-dev": [ @@ -7857,315 +8232,108 @@ }, { "name": "doctrine/annotations", - "version": "1.13.2", + "version": "1.13.3", "source": { "type": "git", "url": "https://github.com/doctrine/annotations.git", - "reference": "5b668aef16090008790395c02c893b1ba13f7e08" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/annotations/zipball/5b668aef16090008790395c02c893b1ba13f7e08", - "reference": "5b668aef16090008790395c02c893b1ba13f7e08", - "shasum": "" - }, - "require": { - "doctrine/lexer": "1.*", - "ext-tokenizer": "*", - "php": "^7.1 || ^8.0", - "psr/cache": "^1 || ^2 || ^3" - }, - "require-dev": { - "doctrine/cache": "^1.11 || ^2.0", - "doctrine/coding-standard": "^6.0 || ^8.1", - "phpstan/phpstan": "^0.12.20", - "phpunit/phpunit": "^7.5 || ^8.0 || ^9.1.5", - "symfony/cache": "^4.4 || ^5.2" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - } - ], - "description": "Docblock Annotations Parser", - "homepage": "https://www.doctrine-project.org/projects/annotations.html", - "keywords": [ - "annotations", - "docblock", - "parser" - ], - "support": { - "issues": "https://github.com/doctrine/annotations/issues", - "source": "https://github.com/doctrine/annotations/tree/1.13.2" - }, - "time": "2021-08-05T19:00:23+00:00" - }, - { - "name": "doctrine/instantiator", - "version": "1.4.1", - "source": { - "type": "git", - "url": "https://github.com/doctrine/instantiator.git", - "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/10dcfce151b967d20fde1b34ae6640712c3891bc", - "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "require-dev": { - "doctrine/coding-standard": "^9", - "ext-pdo": "*", - "ext-phar": "*", - "phpbench/phpbench": "^0.16 || ^1", - "phpstan/phpstan": "^1.4", - "phpstan/phpstan-phpunit": "^1", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "vimeo/psalm": "^4.22" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com", - "homepage": "https://ocramius.github.io/" - } - ], - "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", - "homepage": "https://www.doctrine-project.org/projects/instantiator.html", - "keywords": [ - "constructor", - "instantiate" - ], - "support": { - "issues": "https://github.com/doctrine/instantiator/issues", - "source": "https://github.com/doctrine/instantiator/tree/1.4.1" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", - "type": "tidelift" - } - ], - "time": "2022-03-03T08:28:38+00:00" - }, - { - "name": "facade/flare-client-php", - "version": "1.9.1", - "source": { - "type": "git", - "url": "https://github.com/facade/flare-client-php.git", - "reference": "b2adf1512755637d0cef4f7d1b54301325ac78ed" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/facade/flare-client-php/zipball/b2adf1512755637d0cef4f7d1b54301325ac78ed", - "reference": "b2adf1512755637d0cef4f7d1b54301325ac78ed", - "shasum": "" - }, - "require": { - "facade/ignition-contracts": "~1.0", - "illuminate/pipeline": "^5.5|^6.0|^7.0|^8.0", - "php": "^7.1|^8.0", - "symfony/http-foundation": "^3.3|^4.1|^5.0", - "symfony/mime": "^3.4|^4.0|^5.1", - "symfony/var-dumper": "^3.4|^4.0|^5.0" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^2.14", - "phpunit/phpunit": "^7.5.16", - "spatie/phpunit-snapshot-assertions": "^2.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "autoload": { - "files": [ - "src/helpers.php" - ], - "psr-4": { - "Facade\\FlareClient\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Send PHP errors to Flare", - "homepage": "https://github.com/facade/flare-client-php", - "keywords": [ - "exception", - "facade", - "flare", - "reporting" - ], - "support": { - "issues": "https://github.com/facade/flare-client-php/issues", - "source": "https://github.com/facade/flare-client-php/tree/1.9.1" - }, - "funding": [ - { - "url": "https://github.com/spatie", - "type": "github" - } - ], - "time": "2021-09-13T12:16:46+00:00" - }, - { - "name": "facade/ignition", - "version": "2.17.5", - "source": { - "type": "git", - "url": "https://github.com/facade/ignition.git", - "reference": "1d71996f83c9a5a7807331b8986ac890352b7a0c" + "reference": "648b0343343565c4a056bfc8392201385e8d89f0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/facade/ignition/zipball/1d71996f83c9a5a7807331b8986ac890352b7a0c", - "reference": "1d71996f83c9a5a7807331b8986ac890352b7a0c", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/648b0343343565c4a056bfc8392201385e8d89f0", + "reference": "648b0343343565c4a056bfc8392201385e8d89f0", "shasum": "" }, - "require": { - "ext-curl": "*", - "ext-json": "*", - "ext-mbstring": "*", - "facade/flare-client-php": "^1.9.1", - "facade/ignition-contracts": "^1.0.2", - "illuminate/support": "^7.0|^8.0", - "monolog/monolog": "^2.0", - "php": "^7.2.5|^8.0", - "symfony/console": "^5.0", - "symfony/var-dumper": "^5.0" + "require": { + "doctrine/lexer": "1.*", + "ext-tokenizer": "*", + "php": "^7.1 || ^8.0", + "psr/cache": "^1 || ^2 || ^3" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^2.14", - "livewire/livewire": "^2.4", - "mockery/mockery": "^1.3", - "orchestra/testbench": "^5.0|^6.0", - "psalm/plugin-laravel": "^1.2" - }, - "suggest": { - "laravel/telescope": "^3.1" + "doctrine/cache": "^1.11 || ^2.0", + "doctrine/coding-standard": "^6.0 || ^8.1", + "phpstan/phpstan": "^1.4.10 || ^1.8.0", + "phpunit/phpunit": "^7.5 || ^8.0 || ^9.1.5", + "symfony/cache": "^4.4 || ^5.2", + "vimeo/psalm": "^4.10" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.x-dev" - }, - "laravel": { - "providers": [ - "Facade\\Ignition\\IgnitionServiceProvider" - ], - "aliases": { - "Flare": "Facade\\Ignition\\Facades\\Flare" - } - } - }, "autoload": { - "files": [ - "src/helpers.php" - ], "psr-4": { - "Facade\\Ignition\\": "src" + "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "description": "A beautiful error page for Laravel applications.", - "homepage": "https://github.com/facade/ignition", + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Docblock Annotations Parser", + "homepage": "https://www.doctrine-project.org/projects/annotations.html", "keywords": [ - "error", - "flare", - "laravel", - "page" + "annotations", + "docblock", + "parser" ], "support": { - "docs": "https://flareapp.io/docs/ignition-for-laravel/introduction", - "forum": "https://twitter.com/flareappio", - "issues": "https://github.com/facade/ignition/issues", - "source": "https://github.com/facade/ignition" + "issues": "https://github.com/doctrine/annotations/issues", + "source": "https://github.com/doctrine/annotations/tree/1.13.3" }, - "time": "2022-02-23T18:31:24+00:00" + "time": "2022-07-02T10:48:51+00:00" }, { - "name": "facade/ignition-contracts", - "version": "1.0.2", + "name": "doctrine/instantiator", + "version": "1.4.1", "source": { "type": "git", - "url": "https://github.com/facade/ignition-contracts.git", - "reference": "3c921a1cdba35b68a7f0ccffc6dffc1995b18267" + "url": "https://github.com/doctrine/instantiator.git", + "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/facade/ignition-contracts/zipball/3c921a1cdba35b68a7f0ccffc6dffc1995b18267", - "reference": "3c921a1cdba35b68a7f0ccffc6dffc1995b18267", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/10dcfce151b967d20fde1b34ae6640712c3891bc", + "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc", "shasum": "" }, "require": { - "php": "^7.3|^8.0" + "php": "^7.1 || ^8.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^v2.15.8", - "phpunit/phpunit": "^9.3.11", - "vimeo/psalm": "^3.17.1" + "doctrine/coding-standard": "^9", + "ext-pdo": "*", + "ext-phar": "*", + "phpbench/phpbench": "^0.16 || ^1", + "phpstan/phpstan": "^1.4", + "phpstan/phpstan-phpunit": "^1", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "vimeo/psalm": "^4.22" }, "type": "library", "autoload": { "psr-4": { - "Facade\\IgnitionContracts\\": "src" + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" } }, "notification-url": "https://packagist.org/downloads/", @@ -8174,37 +8342,49 @@ ], "authors": [ { - "name": "Freek Van der Herten", - "email": "freek@spatie.be", - "homepage": "https://flareapp.io", - "role": "Developer" + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "https://ocramius.github.io/" } ], - "description": "Solution contracts for Ignition", - "homepage": "https://github.com/facade/ignition-contracts", + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", "keywords": [ - "contracts", - "flare", - "ignition" + "constructor", + "instantiate" ], "support": { - "issues": "https://github.com/facade/ignition-contracts/issues", - "source": "https://github.com/facade/ignition-contracts/tree/1.0.2" + "issues": "https://github.com/doctrine/instantiator/issues", + "source": "https://github.com/doctrine/instantiator/tree/1.4.1" }, - "time": "2020-10-16T08:27:54+00:00" + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", + "type": "tidelift" + } + ], + "time": "2022-03-03T08:28:38+00:00" }, { "name": "fakerphp/faker", - "version": "v1.19.0", + "version": "v1.20.0", "source": { "type": "git", "url": "https://github.com/FakerPHP/Faker.git", - "reference": "d7f08a622b3346766325488aa32ddc93ccdecc75" + "reference": "37f751c67a5372d4e26353bd9384bc03744ec77b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/d7f08a622b3346766325488aa32ddc93ccdecc75", - "reference": "d7f08a622b3346766325488aa32ddc93ccdecc75", + "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/37f751c67a5372d4e26353bd9384bc03744ec77b", + "reference": "37f751c67a5372d4e26353bd9384bc03744ec77b", "shasum": "" }, "require": { @@ -8231,7 +8411,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "v1.19-dev" + "dev-main": "v1.20-dev" } }, "autoload": { @@ -8256,9 +8436,9 @@ ], "support": { "issues": "https://github.com/FakerPHP/Faker/issues", - "source": "https://github.com/FakerPHP/Faker/tree/v1.19.0" + "source": "https://github.com/FakerPHP/Faker/tree/v1.20.0" }, - "time": "2022-02-02T17:38:57+00:00" + "time": "2022-07-20T13:12:54+00:00" }, { "name": "filp/whoops", @@ -8333,16 +8513,16 @@ }, { "name": "friendsofphp/php-cs-fixer", - "version": "v3.8.0", + "version": "v3.11.0", "source": { "type": "git", "url": "https://github.com/FriendsOfPHP/PHP-CS-Fixer.git", - "reference": "cbad1115aac4b5c3c5540e7210d3c9fba2f81fa3" + "reference": "7dcdea3f2f5f473464e835be9be55283ff8cfdc3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/cbad1115aac4b5c3c5540e7210d3c9fba2f81fa3", - "reference": "cbad1115aac4b5c3c5540e7210d3c9fba2f81fa3", + "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/7dcdea3f2f5f473464e835be9be55283ff8cfdc3", + "reference": "7dcdea3f2f5f473464e835be9be55283ff8cfdc3", "shasum": "" }, "require": { @@ -8352,7 +8532,7 @@ "ext-json": "*", "ext-tokenizer": "*", "php": "^7.4 || ^8.0", - "php-cs-fixer/diff": "^2.0", + "sebastian/diff": "^4.0", "symfony/console": "^5.4 || ^6.0", "symfony/event-dispatcher": "^5.4 || ^6.0", "symfony/filesystem": "^5.4 || ^6.0", @@ -8410,7 +8590,7 @@ "description": "A tool to automatically fix PHP code style", "support": { "issues": "https://github.com/FriendsOfPHP/PHP-CS-Fixer/issues", - "source": "https://github.com/FriendsOfPHP/PHP-CS-Fixer/tree/v3.8.0" + "source": "https://github.com/FriendsOfPHP/PHP-CS-Fixer/tree/v3.11.0" }, "funding": [ { @@ -8418,7 +8598,7 @@ "type": "github" } ], - "time": "2022-03-18T17:20:59+00:00" + "time": "2022-09-01T18:24:51+00:00" }, { "name": "hamcrest/hamcrest-php", @@ -8473,16 +8653,16 @@ }, { "name": "itsgoingd/clockwork", - "version": "v5.1.6", + "version": "v5.1.8", "source": { "type": "git", "url": "https://github.com/itsgoingd/clockwork.git", - "reference": "9df41432da1d8cb39c7fda383ddcc02231c83ff3" + "reference": "74ee05a61296aa7298164ef5346f0a568aa6106e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/itsgoingd/clockwork/zipball/9df41432da1d8cb39c7fda383ddcc02231c83ff3", - "reference": "9df41432da1d8cb39c7fda383ddcc02231c83ff3", + "url": "https://api.github.com/repos/itsgoingd/clockwork/zipball/74ee05a61296aa7298164ef5346f0a568aa6106e", + "reference": "74ee05a61296aa7298164ef5346f0a568aa6106e", "shasum": "" }, "require": { @@ -8529,7 +8709,7 @@ ], "support": { "issues": "https://github.com/itsgoingd/clockwork/issues", - "source": "https://github.com/itsgoingd/clockwork/tree/v5.1.6" + "source": "https://github.com/itsgoingd/clockwork/tree/v5.1.8" }, "funding": [ { @@ -8537,20 +8717,20 @@ "type": "github" } ], - "time": "2022-04-12T21:35:47+00:00" + "time": "2022-09-25T20:21:14+00:00" }, { "name": "mockery/mockery", - "version": "1.5.0", + "version": "1.5.1", "source": { "type": "git", "url": "https://github.com/mockery/mockery.git", - "reference": "c10a5f6e06fc2470ab1822fa13fa2a7380f8fbac" + "reference": "e92dcc83d5a51851baf5f5591d32cb2b16e3684e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/mockery/mockery/zipball/c10a5f6e06fc2470ab1822fa13fa2a7380f8fbac", - "reference": "c10a5f6e06fc2470ab1822fa13fa2a7380f8fbac", + "url": "https://api.github.com/repos/mockery/mockery/zipball/e92dcc83d5a51851baf5f5591d32cb2b16e3684e", + "reference": "e92dcc83d5a51851baf5f5591d32cb2b16e3684e", "shasum": "" }, "require": { @@ -8607,9 +8787,9 @@ ], "support": { "issues": "https://github.com/mockery/mockery/issues", - "source": "https://github.com/mockery/mockery/tree/1.5.0" + "source": "https://github.com/mockery/mockery/tree/1.5.1" }, - "time": "2022-01-20T13:18:17+00:00" + "time": "2022-09-07T15:32:08+00:00" }, { "name": "myclabs/deep-copy", @@ -8672,37 +8852,38 @@ }, { "name": "nunomaduro/collision", - "version": "v5.11.0", + "version": "v6.3.1", "source": { "type": "git", "url": "https://github.com/nunomaduro/collision.git", - "reference": "8b610eef8582ccdc05d8f2ab23305e2d37049461" + "reference": "0f6349c3ed5dd28467087b08fb59384bb458a22b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/collision/zipball/8b610eef8582ccdc05d8f2ab23305e2d37049461", - "reference": "8b610eef8582ccdc05d8f2ab23305e2d37049461", + "url": "https://api.github.com/repos/nunomaduro/collision/zipball/0f6349c3ed5dd28467087b08fb59384bb458a22b", + "reference": "0f6349c3ed5dd28467087b08fb59384bb458a22b", "shasum": "" }, "require": { - "facade/ignition-contracts": "^1.0", - "filp/whoops": "^2.14.3", - "php": "^7.3 || ^8.0", - "symfony/console": "^5.0" + "filp/whoops": "^2.14.5", + "php": "^8.0.0", + "symfony/console": "^6.0.2" }, "require-dev": { - "brianium/paratest": "^6.1", - "fideloper/proxy": "^4.4.1", - "fruitcake/laravel-cors": "^2.0.3", - "laravel/framework": "8.x-dev", - "nunomaduro/larastan": "^0.6.2", - "nunomaduro/mock-final-classes": "^1.0", - "orchestra/testbench": "^6.0", - "phpstan/phpstan": "^0.12.64", - "phpunit/phpunit": "^9.5.0" + "brianium/paratest": "^6.4.1", + "laravel/framework": "^9.26.1", + "laravel/pint": "^1.1.1", + "nunomaduro/larastan": "^1.0.3", + "nunomaduro/mock-final-classes": "^1.1.0", + "orchestra/testbench": "^7.7", + "phpunit/phpunit": "^9.5.23", + "spatie/ignition": "^1.4.1" }, "type": "library", "extra": { + "branch-alias": { + "dev-develop": "6.x-dev" + }, "laravel": { "providers": [ "NunoMaduro\\Collision\\Adapters\\Laravel\\CollisionServiceProvider" @@ -8755,7 +8936,7 @@ "type": "patreon" } ], - "time": "2022-01-10T16:22:52+00:00" + "time": "2022-09-29T12:29:49+00:00" }, { "name": "phar-io/manifest", @@ -8868,58 +9049,6 @@ }, "time": "2022-02-21T01:04:05+00:00" }, - { - "name": "php-cs-fixer/diff", - "version": "v2.0.2", - "source": { - "type": "git", - "url": "https://github.com/PHP-CS-Fixer/diff.git", - "reference": "29dc0d507e838c4580d018bd8b5cb412474f7ec3" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/diff/zipball/29dc0d507e838c4580d018bd8b5cb412474f7ec3", - "reference": "29dc0d507e838c4580d018bd8b5cb412474f7ec3", - "shasum": "" - }, - "require": { - "php": "^5.6 || ^7.0 || ^8.0" - }, - "require-dev": { - "phpunit/phpunit": "^5.7.23 || ^6.4.3 || ^7.0", - "symfony/process": "^3.3" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Kore Nordmann", - "email": "mail@kore-nordmann.de" - } - ], - "description": "sebastian/diff v3 backport support for PHP 5.6+", - "homepage": "https://github.com/PHP-CS-Fixer", - "keywords": [ - "diff" - ], - "support": { - "issues": "https://github.com/PHP-CS-Fixer/diff/issues", - "source": "https://github.com/PHP-CS-Fixer/diff/tree/v2.0.2" - }, - "time": "2020-10-14T08:32:19+00:00" - }, { "name": "php-mock/php-mock", "version": "2.3.1", @@ -9054,16 +9183,16 @@ }, { "name": "php-mock/php-mock-phpunit", - "version": "2.6.0", + "version": "2.6.1", "source": { "type": "git", "url": "https://github.com/php-mock/php-mock-phpunit.git", - "reference": "2877a0e58f12e91b64bf36ccd080a209dcbf6c30" + "reference": "b9ba2db21e7e1c7deba98bc86dcfc6425fb4647d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-mock/php-mock-phpunit/zipball/2877a0e58f12e91b64bf36ccd080a209dcbf6c30", - "reference": "2877a0e58f12e91b64bf36ccd080a209dcbf6c30", + "url": "https://api.github.com/repos/php-mock/php-mock-phpunit/zipball/b9ba2db21e7e1c7deba98bc86dcfc6425fb4647d", + "reference": "b9ba2db21e7e1c7deba98bc86dcfc6425fb4647d", "shasum": "" }, "require": { @@ -9071,6 +9200,9 @@ "php-mock/php-mock-integration": "^2.1", "phpunit/phpunit": "^6 || ^7 || ^8 || ^9" }, + "require-dev": { + "phpspec/prophecy": "^1.10.3" + }, "type": "library", "autoload": { "files": [ @@ -9106,9 +9238,15 @@ ], "support": { "issues": "https://github.com/php-mock/php-mock-phpunit/issues", - "source": "https://github.com/php-mock/php-mock-phpunit/tree/2.6.0" + "source": "https://github.com/php-mock/php-mock-phpunit/tree/2.6.1" }, - "time": "2020-02-08T15:44:47+00:00" + "funding": [ + { + "url": "https://github.com/michalbundyra", + "type": "github" + } + ], + "time": "2022-09-07T20:40:07+00:00" }, { "name": "phpdocumentor/reflection-common", @@ -9151,159 +9289,49 @@ "description": "Common reflection classes used by phpdocumentor to reflect the code structure", "homepage": "http://www.phpdoc.org", "keywords": [ - "FQSEN", - "phpDocumentor", - "phpdoc", - "reflection", - "static analysis" - ], - "support": { - "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", - "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" - }, - "time": "2020-06-27T09:03:43+00:00" - }, - { - "name": "phpdocumentor/reflection-docblock", - "version": "5.3.0", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "622548b623e81ca6d78b721c5e029f4ce664f170" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/622548b623e81ca6d78b721c5e029f4ce664f170", - "reference": "622548b623e81ca6d78b721c5e029f4ce664f170", - "shasum": "" - }, - "require": { - "ext-filter": "*", - "php": "^7.2 || ^8.0", - "phpdocumentor/reflection-common": "^2.2", - "phpdocumentor/type-resolver": "^1.3", - "webmozart/assert": "^1.9.1" - }, - "require-dev": { - "mockery/mockery": "~1.3.2", - "psalm/phar": "^4.8" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.x-dev" - } - }, - "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Mike van Riel", - "email": "me@mikevanriel.com" - }, - { - "name": "Jaap van Otterdijk", - "email": "account@ijaap.nl" - } - ], - "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", - "support": { - "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", - "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.3.0" - }, - "time": "2021-10-19T17:43:47+00:00" - }, - { - "name": "phpdocumentor/type-resolver", - "version": "1.6.1", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "77a32518733312af16a44300404e945338981de3" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/77a32518733312af16a44300404e945338981de3", - "reference": "77a32518733312af16a44300404e945338981de3", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0", - "phpdocumentor/reflection-common": "^2.0" - }, - "require-dev": { - "ext-tokenizer": "*", - "psalm/phar": "^4.8" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-1.x": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Mike van Riel", - "email": "me@mikevanriel.com" - } + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" ], - "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", "support": { - "issues": "https://github.com/phpDocumentor/TypeResolver/issues", - "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.6.1" + "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", + "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" }, - "time": "2022-03-15T21:29:03+00:00" + "time": "2020-06-27T09:03:43+00:00" }, { - "name": "phpspec/prophecy", - "version": "v1.15.0", + "name": "phpdocumentor/type-resolver", + "version": "1.6.1", "source": { "type": "git", - "url": "https://github.com/phpspec/prophecy.git", - "reference": "bbcd7380b0ebf3961ee21409db7b38bc31d69a13" + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "77a32518733312af16a44300404e945338981de3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/bbcd7380b0ebf3961ee21409db7b38bc31d69a13", - "reference": "bbcd7380b0ebf3961ee21409db7b38bc31d69a13", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/77a32518733312af16a44300404e945338981de3", + "reference": "77a32518733312af16a44300404e945338981de3", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.2", - "php": "^7.2 || ~8.0, <8.2", - "phpdocumentor/reflection-docblock": "^5.2", - "sebastian/comparator": "^3.0 || ^4.0", - "sebastian/recursion-context": "^3.0 || ^4.0" + "php": "^7.2 || ^8.0", + "phpdocumentor/reflection-common": "^2.0" }, "require-dev": { - "phpspec/phpspec": "^6.0 || ^7.0", - "phpunit/phpunit": "^8.0 || ^9.0" + "ext-tokenizer": "*", + "psalm/phar": "^4.8" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.x-dev" + "dev-1.x": "1.x-dev" } }, "autoload": { "psr-4": { - "Prophecy\\": "src/Prophecy" + "phpDocumentor\\Reflection\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -9312,50 +9340,36 @@ ], "authors": [ { - "name": "Konstantin Kudryashov", - "email": "ever.zet@gmail.com", - "homepage": "http://everzet.com" - }, - { - "name": "Marcello Duarte", - "email": "marcello.duarte@gmail.com" + "name": "Mike van Riel", + "email": "me@mikevanriel.com" } ], - "description": "Highly opinionated mocking framework for PHP 5.3+", - "homepage": "https://github.com/phpspec/prophecy", - "keywords": [ - "Double", - "Dummy", - "fake", - "mock", - "spy", - "stub" - ], + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", "support": { - "issues": "https://github.com/phpspec/prophecy/issues", - "source": "https://github.com/phpspec/prophecy/tree/v1.15.0" + "issues": "https://github.com/phpDocumentor/TypeResolver/issues", + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.6.1" }, - "time": "2021-12-08T12:19:24+00:00" + "time": "2022-03-15T21:29:03+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "9.2.15", + "version": "9.2.17", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "2e9da11878c4202f97915c1cb4bb1ca318a63f5f" + "reference": "aa94dc41e8661fe90c7316849907cba3007b10d8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/2e9da11878c4202f97915c1cb4bb1ca318a63f5f", - "reference": "2e9da11878c4202f97915c1cb4bb1ca318a63f5f", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/aa94dc41e8661fe90c7316849907cba3007b10d8", + "reference": "aa94dc41e8661fe90c7316849907cba3007b10d8", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^4.13.0", + "nikic/php-parser": "^4.14", "php": ">=7.3", "phpunit/php-file-iterator": "^3.0.3", "phpunit/php-text-template": "^2.0.2", @@ -9404,7 +9418,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.15" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.17" }, "funding": [ { @@ -9412,7 +9426,7 @@ "type": "github" } ], - "time": "2022-03-07T09:28:20+00:00" + "time": "2022-08-30T12:24:04+00:00" }, { "name": "phpunit/php-file-iterator", @@ -9657,16 +9671,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.5.20", + "version": "9.5.25", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "12bc8879fb65aef2138b26fc633cb1e3620cffba" + "reference": "3e6f90ca7e3d02025b1d147bd8d4a89fd4ca8a1d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/12bc8879fb65aef2138b26fc633cb1e3620cffba", - "reference": "12bc8879fb65aef2138b26fc633cb1e3620cffba", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3e6f90ca7e3d02025b1d147bd8d4a89fd4ca8a1d", + "reference": "3e6f90ca7e3d02025b1d147bd8d4a89fd4ca8a1d", "shasum": "" }, "require": { @@ -9681,7 +9695,6 @@ "phar-io/manifest": "^2.0.3", "phar-io/version": "^3.0.2", "php": ">=7.3", - "phpspec/prophecy": "^1.12.1", "phpunit/php-code-coverage": "^9.2.13", "phpunit/php-file-iterator": "^3.0.5", "phpunit/php-invoker": "^3.1.1", @@ -9689,20 +9702,16 @@ "phpunit/php-timer": "^5.0.2", "sebastian/cli-parser": "^1.0.1", "sebastian/code-unit": "^1.0.6", - "sebastian/comparator": "^4.0.5", + "sebastian/comparator": "^4.0.8", "sebastian/diff": "^4.0.3", "sebastian/environment": "^5.1.3", - "sebastian/exporter": "^4.0.3", + "sebastian/exporter": "^4.0.5", "sebastian/global-state": "^5.0.1", "sebastian/object-enumerator": "^4.0.3", "sebastian/resource-operations": "^3.0.3", - "sebastian/type": "^3.0", + "sebastian/type": "^3.2", "sebastian/version": "^3.0.2" }, - "require-dev": { - "ext-pdo": "*", - "phpspec/prophecy-phpunit": "^2.0.1" - }, "suggest": { "ext-soap": "*", "ext-xdebug": "*" @@ -9744,7 +9753,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.20" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.25" }, "funding": [ { @@ -9754,9 +9763,13 @@ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", + "type": "tidelift" } ], - "time": "2022-04-01T12:37:26+00:00" + "time": "2022-09-25T03:44:45+00:00" }, { "name": "sebastian/cli-parser", @@ -9927,16 +9940,16 @@ }, { "name": "sebastian/comparator", - "version": "4.0.6", + "version": "4.0.8", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "55f4261989e546dc112258c7a75935a81a7ce382" + "reference": "fa0f136dd2334583309d32b62544682ee972b51a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/55f4261989e546dc112258c7a75935a81a7ce382", - "reference": "55f4261989e546dc112258c7a75935a81a7ce382", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/fa0f136dd2334583309d32b62544682ee972b51a", + "reference": "fa0f136dd2334583309d32b62544682ee972b51a", "shasum": "" }, "require": { @@ -9989,7 +10002,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", - "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.6" + "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.8" }, "funding": [ { @@ -9997,7 +10010,7 @@ "type": "github" } ], - "time": "2020-10-26T15:49:45+00:00" + "time": "2022-09-14T12:41:17+00:00" }, { "name": "sebastian/complexity", @@ -10187,16 +10200,16 @@ }, { "name": "sebastian/exporter", - "version": "4.0.4", + "version": "4.0.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "65e8b7db476c5dd267e65eea9cab77584d3cfff9" + "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/65e8b7db476c5dd267e65eea9cab77584d3cfff9", - "reference": "65e8b7db476c5dd267e65eea9cab77584d3cfff9", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", + "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", "shasum": "" }, "require": { @@ -10252,7 +10265,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", - "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.4" + "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.5" }, "funding": [ { @@ -10260,7 +10273,7 @@ "type": "github" } ], - "time": "2021-11-11T14:18:36+00:00" + "time": "2022-09-14T06:03:37+00:00" }, { "name": "sebastian/global-state", @@ -10615,16 +10628,16 @@ }, { "name": "sebastian/type", - "version": "3.0.0", + "version": "3.2.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", - "reference": "b233b84bc4465aff7b57cf1c4bc75c86d00d6dad" + "reference": "fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/b233b84bc4465aff7b57cf1c4bc75c86d00d6dad", - "reference": "b233b84bc4465aff7b57cf1c4bc75c86d00d6dad", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e", + "reference": "fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e", "shasum": "" }, "require": { @@ -10636,7 +10649,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-master": "3.2-dev" } }, "autoload": { @@ -10659,7 +10672,7 @@ "homepage": "https://github.com/sebastianbergmann/type", "support": { "issues": "https://github.com/sebastianbergmann/type/issues", - "source": "https://github.com/sebastianbergmann/type/tree/3.0.0" + "source": "https://github.com/sebastianbergmann/type/tree/3.2.0" }, "funding": [ { @@ -10667,7 +10680,7 @@ "type": "github" } ], - "time": "2022-03-15T09:54:48+00:00" + "time": "2022-09-12T14:47:03+00:00" }, { "name": "sebastian/version", @@ -10722,25 +10735,320 @@ ], "time": "2020-09-28T06:39:44+00:00" }, + { + "name": "spatie/backtrace", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/spatie/backtrace.git", + "reference": "4ee7d41aa5268107906ea8a4d9ceccde136dbd5b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/backtrace/zipball/4ee7d41aa5268107906ea8a4d9ceccde136dbd5b", + "reference": "4ee7d41aa5268107906ea8a4d9ceccde136dbd5b", + "shasum": "" + }, + "require": { + "php": "^7.3|^8.0" + }, + "require-dev": { + "ext-json": "*", + "phpunit/phpunit": "^9.3", + "symfony/var-dumper": "^5.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Spatie\\Backtrace\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van de Herten", + "email": "freek@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "A better backtrace", + "homepage": "https://github.com/spatie/backtrace", + "keywords": [ + "Backtrace", + "spatie" + ], + "support": { + "issues": "https://github.com/spatie/backtrace/issues", + "source": "https://github.com/spatie/backtrace/tree/1.2.1" + }, + "funding": [ + { + "url": "https://github.com/sponsors/spatie", + "type": "github" + }, + { + "url": "https://spatie.be/open-source/support-us", + "type": "other" + } + ], + "time": "2021-11-09T10:57:15+00:00" + }, + { + "name": "spatie/flare-client-php", + "version": "1.3.0", + "source": { + "type": "git", + "url": "https://github.com/spatie/flare-client-php.git", + "reference": "b1b974348750925b717fa8c8b97a0db0d1aa40ca" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/flare-client-php/zipball/b1b974348750925b717fa8c8b97a0db0d1aa40ca", + "reference": "b1b974348750925b717fa8c8b97a0db0d1aa40ca", + "shasum": "" + }, + "require": { + "illuminate/pipeline": "^8.0|^9.0", + "php": "^8.0", + "spatie/backtrace": "^1.2", + "symfony/http-foundation": "^5.0|^6.0", + "symfony/mime": "^5.2|^6.0", + "symfony/process": "^5.2|^6.0", + "symfony/var-dumper": "^5.2|^6.0" + }, + "require-dev": { + "dms/phpunit-arraysubset-asserts": "^0.3.0", + "pestphp/pest": "^1.20", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan-deprecation-rules": "^1.0", + "phpstan/phpstan-phpunit": "^1.0", + "spatie/phpunit-snapshot-assertions": "^4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.1.x-dev" + } + }, + "autoload": { + "files": [ + "src/helpers.php" + ], + "psr-4": { + "Spatie\\FlareClient\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Send PHP errors to Flare", + "homepage": "https://github.com/spatie/flare-client-php", + "keywords": [ + "exception", + "flare", + "reporting", + "spatie" + ], + "support": { + "issues": "https://github.com/spatie/flare-client-php/issues", + "source": "https://github.com/spatie/flare-client-php/tree/1.3.0" + }, + "funding": [ + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2022-08-08T10:10:20+00:00" + }, + { + "name": "spatie/ignition", + "version": "1.4.1", + "source": { + "type": "git", + "url": "https://github.com/spatie/ignition.git", + "reference": "dd3d456779108d7078baf4e43f8c2b937d9794a1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/ignition/zipball/dd3d456779108d7078baf4e43f8c2b937d9794a1", + "reference": "dd3d456779108d7078baf4e43f8c2b937d9794a1", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-mbstring": "*", + "monolog/monolog": "^2.0", + "php": "^8.0", + "spatie/flare-client-php": "^1.1", + "symfony/console": "^5.4|^6.0", + "symfony/var-dumper": "^5.4|^6.0" + }, + "require-dev": { + "mockery/mockery": "^1.4", + "pestphp/pest": "^1.20", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan-deprecation-rules": "^1.0", + "phpstan/phpstan-phpunit": "^1.0", + "symfony/process": "^5.4|^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Spatie\\Ignition\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Spatie", + "email": "info@spatie.be", + "role": "Developer" + } + ], + "description": "A beautiful error page for PHP applications.", + "homepage": "https://flareapp.io/ignition", + "keywords": [ + "error", + "flare", + "laravel", + "page" + ], + "support": { + "docs": "https://flareapp.io/docs/ignition-for-laravel/introduction", + "forum": "https://twitter.com/flareappio", + "issues": "https://github.com/spatie/ignition/issues", + "source": "https://github.com/spatie/ignition" + }, + "funding": [ + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2022-08-26T11:51:15+00:00" + }, + { + "name": "spatie/laravel-ignition", + "version": "1.5.1", + "source": { + "type": "git", + "url": "https://github.com/spatie/laravel-ignition.git", + "reference": "75d465ec577abb432af1ca9b33683d5a6e921eb9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/laravel-ignition/zipball/75d465ec577abb432af1ca9b33683d5a6e921eb9", + "reference": "75d465ec577abb432af1ca9b33683d5a6e921eb9", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "ext-json": "*", + "ext-mbstring": "*", + "illuminate/support": "^8.77|^9.27", + "monolog/monolog": "^2.3", + "php": "^8.0", + "spatie/flare-client-php": "^1.0.1", + "spatie/ignition": "^1.4.1", + "symfony/console": "^5.0|^6.0", + "symfony/var-dumper": "^5.0|^6.0" + }, + "require-dev": { + "filp/whoops": "^2.14", + "livewire/livewire": "^2.8|dev-develop", + "mockery/mockery": "^1.4", + "nunomaduro/larastan": "^1.0", + "orchestra/testbench": "^6.23|^7.0", + "pestphp/pest": "^1.20", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan-deprecation-rules": "^1.0", + "phpstan/phpstan-phpunit": "^1.0", + "spatie/laravel-ray": "^1.27" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Spatie\\LaravelIgnition\\IgnitionServiceProvider" + ], + "aliases": { + "Flare": "Spatie\\LaravelIgnition\\Facades\\Flare" + } + } + }, + "autoload": { + "files": [ + "src/helpers.php" + ], + "psr-4": { + "Spatie\\LaravelIgnition\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Spatie", + "email": "info@spatie.be", + "role": "Developer" + } + ], + "description": "A beautiful error page for Laravel applications.", + "homepage": "https://flareapp.io/ignition", + "keywords": [ + "error", + "flare", + "laravel", + "page" + ], + "support": { + "docs": "https://flareapp.io/docs/ignition-for-laravel/introduction", + "forum": "https://twitter.com/flareappio", + "issues": "https://github.com/spatie/laravel-ignition/issues", + "source": "https://github.com/spatie/laravel-ignition" + }, + "funding": [ + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2022-10-04T10:14:31+00:00" + }, { "name": "symfony/filesystem", - "version": "v5.4.9", + "version": "v6.0.13", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "36a017fa4cce1eff1b8e8129ff53513abcef05ba" + "reference": "3adca49133bd055ebe6011ed1e012be3c908af79" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/36a017fa4cce1eff1b8e8129ff53513abcef05ba", - "reference": "36a017fa4cce1eff1b8e8129ff53513abcef05ba", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/3adca49133bd055ebe6011ed1e012be3c908af79", + "reference": "3adca49133bd055ebe6011ed1e012be3c908af79", "shasum": "" }, "require": { - "php": ">=7.2.5", + "php": ">=8.0.2", "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-mbstring": "~1.8", - "symfony/polyfill-php80": "^1.16" + "symfony/polyfill-mbstring": "~1.8" }, "type": "library", "autoload": { @@ -10768,7 +11076,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v5.4.9" + "source": "https://github.com/symfony/filesystem/tree/v6.0.13" }, "funding": [ { @@ -10784,27 +11092,25 @@ "type": "tidelift" } ], - "time": "2022-05-20T13:55:35+00:00" + "time": "2022-09-21T20:25:27+00:00" }, { "name": "symfony/options-resolver", - "version": "v5.4.3", + "version": "v6.0.3", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "cc1147cb11af1b43f503ac18f31aa3bec213aba8" + "reference": "51f7006670febe4cbcbae177cbffe93ff833250d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/cc1147cb11af1b43f503ac18f31aa3bec213aba8", - "reference": "cc1147cb11af1b43f503ac18f31aa3bec213aba8", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/51f7006670febe4cbcbae177cbffe93ff833250d", + "reference": "51f7006670febe4cbcbae177cbffe93ff833250d", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/polyfill-php73": "~1.0", - "symfony/polyfill-php80": "^1.16" + "php": ">=8.0.2", + "symfony/deprecation-contracts": "^2.1|^3" }, "type": "library", "autoload": { @@ -10837,7 +11143,7 @@ "options" ], "support": { - "source": "https://github.com/symfony/options-resolver/tree/v5.4.3" + "source": "https://github.com/symfony/options-resolver/tree/v6.0.3" }, "funding": [ { @@ -10853,24 +11159,24 @@ "type": "tidelift" } ], - "time": "2022-01-02T09:53:40+00:00" + "time": "2022-01-02T09:55:41+00:00" }, { "name": "symfony/stopwatch", - "version": "v5.4.5", + "version": "v6.0.13", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "4d04b5c24f3c9a1a168a131f6cbe297155bc0d30" + "reference": "7554fde6848af5ef1178f8ccbdbdb8ae1092c70a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/4d04b5c24f3c9a1a168a131f6cbe297155bc0d30", - "reference": "4d04b5c24f3c9a1a168a131f6cbe297155bc0d30", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/7554fde6848af5ef1178f8ccbdbdb8ae1092c70a", + "reference": "7554fde6848af5ef1178f8ccbdbdb8ae1092c70a", "shasum": "" }, "require": { - "php": ">=7.2.5", + "php": ">=8.0.2", "symfony/service-contracts": "^1|^2|^3" }, "type": "library", @@ -10899,7 +11205,7 @@ "description": "Provides a way to profile code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/stopwatch/tree/v5.4.5" + "source": "https://github.com/symfony/stopwatch/tree/v6.0.13" }, "funding": [ { @@ -10915,7 +11221,7 @@ "type": "tidelift" } ], - "time": "2022-02-18T16:06:09+00:00" + "time": "2022-09-28T15:52:47+00:00" }, { "name": "theseer/tokenizer", @@ -10974,7 +11280,7 @@ "prefer-stable": true, "prefer-lowest": false, "platform": { - "php": "^7.4 || ^8.0 || ^8.1", + "php": "^8.0.2 || ^8.1", "ext-json": "*", "ext-mbstring": "*", "ext-pdo": "*", @@ -10983,7 +11289,7 @@ }, "platform-dev": [], "platform-overrides": { - "php": "7.4.0" + "php": "8.0.2" }, "plugin-api-version": "2.3.0" } diff --git a/config/app.php b/config/app.php index b7513e1392..c3764619a1 100644 --- a/config/app.php +++ b/config/app.php @@ -1,5 +1,7 @@ env('APP_REPORT_ALL_EXCEPTIONS', false), ], + /* + |-------------------------------------------------------------------------- + | Maintenance Mode Driver + |-------------------------------------------------------------------------- + | + | These configuration options determine the driver used to determine and + | manage Laravel's "maintenance mode" status. The "cache" driver will + | allow maintenance mode to be controlled across multiple machines. + | + | Supported drivers: "file", "cache" + | + */ + + 'maintenance' => [ + 'driver' => 'file', + ], + /* |-------------------------------------------------------------------------- | Autoloaded Service Providers @@ -197,52 +216,19 @@ | | This array of class aliases will be registered when this application | is started. However, feel free to register as many as you wish as - | the aliases are "lazy" loaded so they don't hinder performance. + | the aliases are "lazy" loaded, so they don't hinder performance. | */ - 'aliases' => [ + 'aliases' => Facade::defaultAliases()->merge([ 'Alert' => Prologue\Alerts\Facades\Alert::class, - 'App' => Illuminate\Support\Facades\App::class, - 'Artisan' => Illuminate\Support\Facades\Artisan::class, - 'Auth' => Illuminate\Support\Facades\Auth::class, - 'Blade' => Illuminate\Support\Facades\Blade::class, - 'Bus' => Illuminate\Support\Facades\Bus::class, - 'Cache' => Illuminate\Support\Facades\Cache::class, 'Carbon' => Carbon\Carbon::class, - 'Config' => Illuminate\Support\Facades\Config::class, - 'Cookie' => Illuminate\Support\Facades\Cookie::class, - 'Crypt' => Illuminate\Support\Facades\Crypt::class, - 'DB' => Illuminate\Support\Facades\DB::class, - 'Eloquent' => Illuminate\Database\Eloquent\Model::class, - 'Event' => Illuminate\Support\Facades\Event::class, - 'File' => Illuminate\Support\Facades\File::class, - 'Gate' => Illuminate\Support\Facades\Gate::class, - 'Hash' => Illuminate\Support\Facades\Hash::class, - 'Input' => Illuminate\Support\Facades\Input::class, - 'Javascript' => Laracasts\Utilities\JavaScript\JavaScriptFacade::class, - 'Lang' => Illuminate\Support\Facades\Lang::class, - 'Log' => Illuminate\Support\Facades\Log::class, - 'Mail' => Illuminate\Support\Facades\Mail::class, - 'Notification' => Illuminate\Support\Facades\Notification::class, - 'Password' => Illuminate\Support\Facades\Password::class, - 'Queue' => Illuminate\Support\Facades\Queue::class, - 'Redirect' => Illuminate\Support\Facades\Redirect::class, - 'Redis' => Illuminate\Support\Facades\Redis::class, - 'Request' => Illuminate\Support\Facades\Request::class, - 'Response' => Illuminate\Support\Facades\Response::class, - 'Route' => Illuminate\Support\Facades\Route::class, - 'Schema' => Illuminate\Support\Facades\Schema::class, - 'Session' => Illuminate\Support\Facades\Session::class, - 'Storage' => Illuminate\Support\Facades\Storage::class, + 'JavaScript' => Laracasts\Utilities\JavaScript\JavaScriptFacade::class, 'Theme' => Pterodactyl\Extensions\Facades\Theme::class, - 'URL' => Illuminate\Support\Facades\URL::class, - 'Validator' => Illuminate\Support\Facades\Validator::class, - 'View' => Illuminate\Support\Facades\View::class, // Custom Facades 'Activity' => Pterodactyl\Facades\Activity::class, 'LogBatch' => Pterodactyl\Facades\LogBatch::class, 'LogTarget' => Pterodactyl\Facades\LogTarget::class, - ], + ])->toArray(), ]; diff --git a/config/auth.php b/config/auth.php index 02f4807e4a..21e6be7b22 100644 --- a/config/auth.php +++ b/config/auth.php @@ -109,6 +109,20 @@ 'provider' => 'users', 'table' => 'password_resets', 'expire' => 60, + 'throttle' => 60, ], ], + + /* + |-------------------------------------------------------------------------- + | Password Confirmation Timeout + |-------------------------------------------------------------------------- + | + | Here you may define the amount of seconds before a password confirmation + | times out and the user is prompted to re-enter their password via the + | confirmation screen. By default, the timeout lasts for three hours. + | + */ + + 'password_timeout' => 10800, ]; diff --git a/config/broadcasting.php b/config/broadcasting.php index 9c4c792def..81add6d891 100644 --- a/config/broadcasting.php +++ b/config/broadcasting.php @@ -10,9 +10,10 @@ | framework when an event needs to be broadcast. You may set this to | any of the connections defined in the "connections" array below. | - | Supported: "pusher", "redis", "log", "null" + | Supported: "pusher", "ably", "redis", "log", "null" | */ + 'default' => env('BROADCAST_DRIVER', 'null'), /* @@ -29,9 +30,24 @@ 'connections' => [ 'pusher' => [ 'driver' => 'pusher', - 'key' => env('PUSHER_KEY'), - 'secret' => env('PUSHER_SECRET'), + 'key' => env('PUSHER_APP_KEY'), + 'secret' => env('PUSHER_APP_SECRET'), 'app_id' => env('PUSHER_APP_ID'), + 'options' => [ + 'host' => env('PUSHER_HOST', 'api-' . env('PUSHER_APP_CLUSTER', 'mt1') . '.pusher.com') ?: 'api-' . env('PUSHER_APP_CLUSTER', 'mt1') . '.pusher.com', + 'port' => env('PUSHER_PORT', 443), + 'scheme' => env('PUSHER_SCHEME', 'https'), + 'encrypted' => true, + 'useTLS' => env('PUSHER_SCHEME', 'https') === 'https', + ], + 'client_options' => [ + // Guzzle client options: https://docs.guzzlephp.org/en/stable/request-options.html + ], + ], + + 'ably' => [ + 'driver' => 'ably', + 'key' => env('ABLY_KEY'), ], 'redis' => [ diff --git a/config/cache.php b/config/cache.php index 27276c2004..14b00b51ba 100644 --- a/config/cache.php +++ b/config/cache.php @@ -1,5 +1,7 @@ env('CACHE_DRIVER', 'redis'), @@ -25,6 +25,9 @@ | well as their drivers. You may even define multiple stores for the | same cache driver to group types of items stored in your caches. | + | Supported drivers: "apc", "array", "database", "file", + | "memcached", "redis", "dynamodb", "octane", "null" + | */ 'stores' => [ @@ -90,11 +93,11 @@ | Cache Key Prefix |-------------------------------------------------------------------------- | - | When utilizing a RAM based store such as APC or Memcached, there might - | be other applications utilizing the same cache. So, we'll specify a - | value to get prefixed to all our keys so we can avoid collisions. + | When utilizing the APC, database, memcached, Redis, or DynamoDB cache + | stores there might be other applications using the same cache. For + | that reason, you may prefix every cache key to avoid collisions. | */ - 'prefix' => env('CACHE_PREFIX', str_slug(env('APP_NAME', 'pterodactyl'), '_') . '_cache'), + 'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'pterodactyl'), '_') . '_cache_'), ]; diff --git a/config/cors.php b/config/cors.php index a96bb270cf..bf72895e06 100644 --- a/config/cors.php +++ b/config/cors.php @@ -3,16 +3,14 @@ return [ /* |-------------------------------------------------------------------------- - | Laravel CORS Options + | Cross-Origin Resource Sharing (CORS) Configuration |-------------------------------------------------------------------------- | - | The allowed_methods and allowed_headers options are case-insensitive. + | Here you may configure your settings for cross-origin resource sharing + | or "CORS". This determines what cross-origin operations may execute + | in web browsers. You are free to adjust these settings as needed. | - | You don't need to provide both allowed_origins and allowed_origins_patterns. - | If one of the strings passed matches, it is considered a valid origin. - | - | If ['*'] is provided to allowed_methods, allowed_origins or allowed_headers - | all methods / origins / headers are allowed. + | To learn more: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS | */ diff --git a/config/database.php b/config/database.php index 27bc6deb2c..b3a460ba28 100644 --- a/config/database.php +++ b/config/database.php @@ -88,13 +88,14 @@ 'options' => [ 'cluster' => env('REDIS_CLUSTER', 'redis'), - 'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_') . '_database_'), + 'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'pterodactyl'), '_') . '_database_'), ], 'default' => [ 'scheme' => env('REDIS_SCHEME', 'tcp'), 'path' => env('REDIS_PATH', '/run/redis/redis.sock'), 'host' => env('REDIS_HOST', 'localhost'), + 'username' => env('REDIS_USERNAME'), 'password' => env('REDIS_PASSWORD'), 'port' => env('REDIS_PORT', 6379), 'database' => env('REDIS_DATABASE', 0), @@ -113,6 +114,7 @@ 'scheme' => env('REDIS_SCHEME', 'tcp'), 'path' => env('REDIS_PATH', '/run/redis/redis.sock'), 'host' => env('REDIS_HOST', 'localhost'), + 'username' => env('REDIS_USERNAME'), 'password' => env('REDIS_PASSWORD'), 'port' => env('REDIS_PORT', 6379), 'database' => env('REDIS_DATABASE_SESSIONS', 1), diff --git a/config/filesystems.php b/config/filesystems.php index bffcc6514d..012bd30b05 100644 --- a/config/filesystems.php +++ b/config/filesystems.php @@ -7,25 +7,12 @@ |-------------------------------------------------------------------------- | | Here you may specify the default filesystem disk that should be used - | by the framework. A "local" driver, as well as a variety of cloud - | based drivers are available for your choosing. Just store away! + | by the framework. The "local" disk, as well as a variety of cloud + | based disks are available to your application. Just store away! | */ - 'default' => env('FILESYSTEM_DRIVER', 'local'), - - /* - |-------------------------------------------------------------------------- - | Default Cloud Filesystem Disk - |-------------------------------------------------------------------------- - | - | Many applications store files both locally and in the cloud. For this - | reason, you may specify a default "cloud" driver here. This driver - | will be bound as the Cloud disk implementation in the container. - | - */ - - 'cloud' => env('FILESYSTEM_CLOUD', 's3'), + 'default' => env('FILESYSTEM_DISK', 'local'), /* |-------------------------------------------------------------------------- @@ -34,15 +21,17 @@ | | Here you may configure as many filesystem "disks" as you wish, and you | may even configure multiple disks of the same driver. Defaults have - | been setup for each driver as an example of the required options. + | been set up for each driver as an example of the required values. | - | Supported Drivers: "local", "ftp", "s3", "rackspace" + | Supported Drivers: "local", "ftp", "sftp", "s3" | */ + 'disks' => [ 'local' => [ 'driver' => 'local', 'root' => storage_path('app'), + 'throw' => false, ], 'public' => [ @@ -50,6 +39,7 @@ 'root' => storage_path('app/public'), 'url' => env('APP_URL') . '/storage', 'visibility' => 'public', + 'throw' => false, ], 's3' => [ @@ -58,8 +48,25 @@ 'secret' => env('AWS_SECRET_ACCESS_KEY'), 'region' => env('AWS_DEFAULT_REGION'), 'bucket' => env('AWS_BUCKET'), + 'url' => env('AWS_URL'), 'endpoint' => env('AWS_ENDPOINT'), 'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false), + 'throw' => false, ], ], + + /* + |-------------------------------------------------------------------------- + | Symbolic Links + |-------------------------------------------------------------------------- + | + | Here you may configure the symbolic links that will be created when the + | `storage:link` Artisan command is executed. The array keys should be + | the locations of the links and the values should be their targets. + | + */ + + 'links' => [ + public_path('storage') => storage_path('app/public'), + ], ]; diff --git a/config/hashing.php b/config/hashing.php index f45a6090ba..552f2bbb46 100644 --- a/config/hashing.php +++ b/config/hashing.php @@ -10,7 +10,7 @@ | passwords for your application. By default, the bcrypt algorithm is | used; however, you remain free to modify this option if you wish. | - | Supported: "bcrypt", "argon" + | Supported: "bcrypt", "argon", "argon2id" | */ @@ -43,8 +43,8 @@ */ 'argon' => [ - 'memory' => 1024, - 'threads' => 2, - 'time' => 2, + 'memory' => 65536, + 'threads' => 1, + 'time' => 4, ], ]; diff --git a/config/logging.php b/config/logging.php index bb4470c0fe..b2cdb82da3 100644 --- a/config/logging.php +++ b/config/logging.php @@ -2,6 +2,7 @@ use Monolog\Handler\NullHandler; use Monolog\Handler\StreamHandler; +use Monolog\Handler\SyslogUdpHandler; return [ /* @@ -17,6 +18,22 @@ 'default' => env('LOG_CHANNEL', 'daily'), + /* + |-------------------------------------------------------------------------- + | Deprecations Log Channel + |-------------------------------------------------------------------------- + | + | This option controls the log channel that should be used to log warnings + | regarding deprecated PHP and library features. This allows you to get + | your application ready for upcoming major versions of dependencies. + | + */ + + 'deprecations' => [ + 'channel' => env('LOG_DEPRECATIONS_CHANNEL', 'null'), + 'trace' => false, + ], + /* |-------------------------------------------------------------------------- | Log Channels @@ -36,18 +53,19 @@ 'stack' => [ 'driver' => 'stack', 'channels' => ['single'], + 'ignore_exceptions' => false, ], 'single' => [ 'driver' => 'single', 'path' => storage_path('logs/laravel.log'), - 'level' => 'debug', + 'level' => env('LOG_LEVEL', 'debug'), ], 'daily' => [ 'driver' => 'daily', 'path' => storage_path('logs/laravel.log'), - 'level' => 'debug', + 'level' => env('LOG_LEVEL', 'debug'), 'days' => 7, ], @@ -56,12 +74,25 @@ 'url' => env('LOG_SLACK_WEBHOOK_URL'), 'username' => 'Laravel Log', 'emoji' => ':boom:', - 'level' => 'critical', + 'level' => env('LOG_LEVEL', 'critical'), + ], + + 'papertrail' => [ + 'driver' => 'monolog', + 'level' => env('LOG_LEVEL', 'debug'), + 'handler' => env('LOG_PAPERTRAIL_HANDLER', SyslogUdpHandler::class), + 'handler_with' => [ + 'host' => env('PAPERTRAIL_URL'), + 'port' => env('PAPERTRAIL_PORT'), + 'connectionString' => 'tls://' . env('PAPERTRAIL_URL') . ':' . env('PAPERTRAIL_PORT'), + ], ], 'stderr' => [ 'driver' => 'monolog', + 'level' => env('LOG_LEVEL', 'debug'), 'handler' => StreamHandler::class, + 'formatter' => env('LOG_STDERR_FORMATTER'), 'with' => [ 'stream' => 'php://stderr', ], @@ -69,17 +100,21 @@ 'syslog' => [ 'driver' => 'syslog', - 'level' => 'debug', + 'level' => env('LOG_LEVEL', 'debug'), ], 'errorlog' => [ 'driver' => 'errorlog', - 'level' => 'debug', + 'level' => env('LOG_LEVEL', 'debug'), ], 'null' => [ 'driver' => 'monolog', 'handler' => NullHandler::class, ], + + 'emergency' => [ + 'path' => storage_path('logs/laravel.log'), + ], ], ]; diff --git a/config/mail.php b/config/mail.php index f47793438a..0f9639291d 100644 --- a/config/mail.php +++ b/config/mail.php @@ -3,126 +3,97 @@ return [ /* |-------------------------------------------------------------------------- - | Mail Driver + | Default Mailer |-------------------------------------------------------------------------- | - | Laravel supports both SMTP and PHP's "mail" function as drivers for the - | sending of e-mail. You may specify which one you're using throughout - | your application here. By default, Laravel is setup for SMTP mail. - | - | Supported: "smtp", "sendmail", "mailgun", "mandrill", "ses", - | "sparkpost", "log", "array" + | This option controls the default mailer that is used to send any email + | messages sent by your application. Alternative mailers may be setup + | and used as needed; however, this mailer will be used by default. | */ - 'driver' => env('MAIL_DRIVER', 'smtp'), + 'default' => env('MAIL_MAILER', env('MAIL_DRIVER', 'smtp')), /* |-------------------------------------------------------------------------- - | SMTP Host Address + | Mailer Configurations |-------------------------------------------------------------------------- | - | Here you may provide the host address of the SMTP server used by your - | applications. A default option is provided that is compatible with - | the Mailgun mail service which will provide reliable deliveries. + | Here you may configure all of the mailers used by your application plus + | their respective settings. Several examples have been configured for + | you and you are free to add your own as your application requires. | - */ - - 'host' => env('MAIL_HOST', 'smtp.mailgun.org'), - - /* - |-------------------------------------------------------------------------- - | SMTP Host Port - |-------------------------------------------------------------------------- + | Laravel supports a variety of mail "transport" drivers to be used while + | sending an e-mail. You will specify which one you are using for your + | mailers below. You are free to add additional mailers as required. | - | This is the SMTP port used by your application to deliver e-mails to - | users of the application. Like the host we have set this value to - | stay compatible with the Mailgun e-mail application by default. + | Supported: "smtp", "sendmail", "mailgun", "ses", + | "postmark", "log", "array", "failover" | */ - 'port' => env('MAIL_PORT', 587), - - /* - |-------------------------------------------------------------------------- - | Global "From" Address - |-------------------------------------------------------------------------- - | - | You may wish for all e-mails sent by your application to be sent from - | the same address. Here, you may specify a name and address that is - | used globally for all e-mails that are sent by your application. - | - */ - - 'from' => [ - 'address' => env('MAIL_FROM'), - 'name' => env('MAIL_FROM_NAME', 'Pterodactyl Panel'), - ], - - /* - |-------------------------------------------------------------------------- - | E-Mail Encryption Protocol - |-------------------------------------------------------------------------- - | - | Here you may specify the encryption protocol that should be used when - | the application send e-mail messages. A sensible default using the - | transport layer security protocol should provide great security. - | - */ + 'mailers' => [ + 'smtp' => [ + 'transport' => 'smtp', + 'host' => env('MAIL_HOST', 'smtp.mailgun.org'), + 'port' => env('MAIL_PORT', 587), + 'encryption' => env('MAIL_ENCRYPTION', 'tls'), + 'username' => env('MAIL_USERNAME'), + 'password' => env('MAIL_PASSWORD'), + 'timeout' => null, + 'local_domain' => env('MAIL_EHLO_DOMAIN'), + ], - 'encryption' => env('MAIL_ENCRYPTION', 'tls'), + 'ses' => [ + 'transport' => 'ses', + ], - /* - |-------------------------------------------------------------------------- - | SMTP Server Username - |-------------------------------------------------------------------------- - | - | If your SMTP server requires a username for authentication, you should - | set it here. This will get used to authenticate with your server on - | connection. You may also set the "password" value below this one. - | - */ + 'mailgun' => [ + 'transport' => 'mailgun', + ], - 'username' => env('MAIL_USERNAME'), + 'postmark' => [ + 'transport' => 'postmark', + ], - /* - |-------------------------------------------------------------------------- - | SMTP Server Password - |-------------------------------------------------------------------------- - | - | Here you may set the password required by your SMTP server to send out - | messages from your application. This will be given to the server on - | connection so that the application will be able to send messages. - | - */ + 'sendmail' => [ + 'transport' => 'sendmail', + 'path' => env('MAIL_SENDMAIL_PATH', '/usr/sbin/sendmail -bs -i'), + ], - 'password' => env('MAIL_PASSWORD'), + 'log' => [ + 'transport' => 'log', + 'channel' => env('MAIL_LOG_CHANNEL'), + ], - /* - |-------------------------------------------------------------------------- - | Sendmail System Path - |-------------------------------------------------------------------------- - | - | When using the "sendmail" driver to send e-mails, we will need to know - | the path to where Sendmail lives on this server. A default path has - | been provided here, which will work well on most of your systems. - | - */ + 'array' => [ + 'transport' => 'array', + ], - 'sendmail' => '/usr/sbin/sendmail -bs', + 'failover' => [ + 'transport' => 'failover', + 'mailers' => [ + 'smtp', + 'log', + ], + ], + ], /* |-------------------------------------------------------------------------- - | Mail "Pretend" + | Global "From" Address |-------------------------------------------------------------------------- | - | When this option is enabled, e-mail will not actually be sent over the - | web and will instead be written to your application's logs files so - | you may inspect the message. This is great for local development. + | You may wish for all e-mails sent by your application to be sent from + | the same address. Here, you may specify a name and address that is + | used globally for all e-mails that are sent by your application. | */ - 'pretend' => false, + 'from' => [ + 'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'), + 'name' => env('MAIL_FROM_NAME', 'Example'), + ], /* |-------------------------------------------------------------------------- @@ -134,6 +105,7 @@ | of the emails. Or, you may simply stick with the Laravel defaults! | */ + 'markdown' => [ 'theme' => 'default', diff --git a/config/pterodactyl.php b/config/pterodactyl.php index 5bca4a3eb7..58c58bc9eb 100644 --- a/config/pterodactyl.php +++ b/config/pterodactyl.php @@ -10,6 +10,7 @@ | setup on the panel. When set to true, configurations stored in the | database will not be applied. */ + 'load_environment_only' => (bool) env('APP_ENVIRONMENT_ONLY', false), /* @@ -21,6 +22,7 @@ | author of custom services, and make upgrades easier by identifying | standard Pterodactyl shipped services. */ + 'service' => [ 'author' => env('APP_SERVICE_AUTHOR', 'unknown@unknown.com'), ], @@ -32,6 +34,7 @@ | | Should login success and failure events trigger an email to the user? */ + 'auth' => [ '2fa_required' => env('APP_2FA_REQUIRED', 0), '2fa' => [ @@ -49,6 +52,7 @@ | Certain pagination result counts can be configured here and will take | effect globally. */ + 'paginate' => [ 'frontend' => [ 'servers' => env('APP_PAGINATE_FRONT_SERVERS', 15), @@ -64,18 +68,6 @@ ], ], - /* - |-------------------------------------------------------------------------- - | API Options - |-------------------------------------------------------------------------- - | - | Configuration options for the API. - */ - 'api' => [ - 'include_on_list' => env('API_INCLUDE_ON_LIST', false), - 'key_expire_time' => env('API_KEY_EXPIRE_TIME', 60 * 12), - ], - /* |-------------------------------------------------------------------------- | Guzzle Connections @@ -83,36 +75,12 @@ | | Configure the timeout to be used for Guzzle connections here. */ + 'guzzle' => [ 'timeout' => env('GUZZLE_TIMEOUT', 15), 'connect_timeout' => env('GUZZLE_CONNECT_TIMEOUT', 5), ], - /* - |-------------------------------------------------------------------------- - | Queue Names - |-------------------------------------------------------------------------- - | - | Configure the names of queues to be used in the database. - */ - 'queues' => [ - 'low' => env('QUEUE_LOW', 'low'), - 'standard' => env('QUEUE_STANDARD', 'standard'), - 'high' => env('QUEUE_HIGH', 'high'), - ], - - /* - |-------------------------------------------------------------------------- - | Task Timers - |-------------------------------------------------------------------------- - | - | The amount of time in minutes before performing certain actions on the system. - */ - 'tasks' => [ - 'clear_log' => env('PTERODACTYL_CLEAR_TASKLOG', 720), - 'delete_server' => env('PTERODACTYL_DELETE_MINUTES', 10), - ], - /* |-------------------------------------------------------------------------- | CDN @@ -121,6 +89,7 @@ | Information for the panel to use when contacting the CDN to confirm | if panel is up to date. */ + 'cdn' => [ 'cache_time' => 60, 'url' => 'https://cdn.pterodactyl.io/releases/latest.json', @@ -133,6 +102,7 @@ | | Allow clients to create their own databases. */ + 'client_features' => [ 'databases' => [ 'enabled' => env('PTERODACTYL_CLIENT_DATABASES_ENABLED', true), @@ -158,26 +128,11 @@ | | This array includes the MIME filetypes that can be edited via the web. */ + 'files' => [ 'max_edit_size' => env('PTERODACTYL_FILES_MAX_EDIT_SIZE', 1024 * 1024 * 4), ], - /* - |-------------------------------------------------------------------------- - | JSON Response Routes - |-------------------------------------------------------------------------- - | - | You should not edit this block. These routes are ajax based routes that - | expect content to be returned in JSON format. - */ - 'json_routes' => [ - 'api/*', - 'daemon/*', - 'remote/*', - ], - - 'default_api_version' => 'application/vnd.pterodactyl.v1+json', - /* |-------------------------------------------------------------------------- | Dynamic Environment Variables @@ -191,6 +146,7 @@ | | 'P_SERVER_CREATED_AT' => 'created_at' */ + 'environment_variables' => [ 'P_SERVER_ALLOCATION_LIMIT' => 'allocation_limit', ], @@ -202,6 +158,7 @@ | | This section controls the output format for JS & CSS assets. */ + 'assets' => [ 'use_hash' => env('PTERODACTYL_USE_ASSET_HASH', false), ], @@ -213,6 +170,7 @@ | | This section controls what notifications are sent to users. */ + 'email' => [ // Should an email be sent to a server owner once their server has completed it's first install process? 'send_install_notification' => env('PTERODACTYL_SEND_INSTALL_NOTIFICATION', true), diff --git a/config/queue.php b/config/queue.php index 02588bb3ec..35585df0e8 100644 --- a/config/queue.php +++ b/config/queue.php @@ -3,14 +3,12 @@ return [ /* |-------------------------------------------------------------------------- - | Default Queue Driver + | Default Queue Connection Name |-------------------------------------------------------------------------- | | Laravel's queue API supports an assortment of back-ends via a single | API, giving you convenient access to each back-end using the same - | syntax for each one. Here you may set the default queue driver. - | - | Supported: "sync", "database", "beanstalkd", "sqs", "redis", "null" + | syntax for every one. Here you may define a default connection. | */ @@ -35,24 +33,29 @@ 'database' => [ 'driver' => 'database', 'table' => 'jobs', - 'queue' => env('QUEUE_STANDARD', 'standard'), + 'queue' => 'default', 'retry_after' => 90, + 'after_commit' => false, ], 'sqs' => [ 'driver' => 'sqs', - 'key' => env('SQS_KEY'), - 'secret' => env('SQS_SECRET'), - 'prefix' => env('SQS_QUEUE_PREFIX'), - 'queue' => env('QUEUE_STANDARD', 'standard'), - 'region' => env('SQS_REGION', 'us-east-1'), + 'key' => env('AWS_ACCESS_KEY_ID'), + 'secret' => env('AWS_SECRET_ACCESS_KEY'), + 'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'), + 'queue' => env('SQS_QUEUE', 'default'), + 'suffix' => env('SQS_SUFFIX'), + 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), + 'after_commit' => false, ], 'redis' => [ 'driver' => 'redis', 'connection' => 'default', - 'queue' => env('QUEUE_STANDARD', 'standard'), + 'queue' => env('REDIS_QUEUE', 'default'), 'retry_after' => 90, + 'block_for' => null, + 'after_commit' => false, ], ], @@ -68,7 +71,8 @@ */ 'failed' => [ - 'database' => 'mysql', + 'driver' => env('QUEUE_FAILED_DRIVER', 'database-uuids'), + 'database' => env('DB_CONNECTION', 'mysql'), 'table' => 'failed_jobs', ], ]; diff --git a/config/services.php b/config/services.php index acaabc5fa4..5cc6e06e25 100644 --- a/config/services.php +++ b/config/services.php @@ -3,33 +3,30 @@ return [ /* |-------------------------------------------------------------------------- - | Third Party Service + | Third Party Services |-------------------------------------------------------------------------- | | This file is for storing the credentials for third party services such - | as Stripe, Mailgun, Mandrill, and others. This file provides a sane - | default location for this type of information, allowing packages - | to have a conventional place to find your various credentials. + | as Mailgun, Postmark, AWS and more. This file provides the de facto + | location for this type of information, allowing packages to have + | a conventional file to locate the various service credentials. | */ 'mailgun' => [ 'domain' => env('MAILGUN_DOMAIN'), 'secret' => env('MAILGUN_SECRET'), - 'endpoint' => env('MAILGUN_ENDPOINT'), + 'endpoint' => env('MAILGUN_ENDPOINT', 'api.mailgun.net'), + 'scheme' => 'https', ], - 'mandrill' => [ - 'secret' => env('MANDRILL_SECRET'), + 'postmark' => [ + 'token' => env('POSTMARK_TOKEN'), ], 'ses' => [ - 'key' => env('SES_KEY'), - 'secret' => env('SES_SECRET'), - 'region' => env('SES_REGION', 'us-east-1'), - ], - - 'sparkpost' => [ - 'secret' => env('SPARKPOST_SECRET'), + 'key' => env('AWS_ACCESS_KEY_ID'), + 'secret' => env('AWS_SECRET_ACCESS_KEY'), + 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), ], ]; diff --git a/config/session.php b/config/session.php index 058528e5f1..d8fb98a099 100644 --- a/config/session.php +++ b/config/session.php @@ -1,5 +1,7 @@ env('SESSION_DRIVER') === 'redis' ? 'sessions' : null, + 'connection' => env('SESSION_CONNECTION'), /* |-------------------------------------------------------------------------- @@ -88,13 +91,15 @@ | Session Cache Store |-------------------------------------------------------------------------- | - | When using the "apc" or "memcached" session drivers, you may specify a - | cache store that should be used for these sessions. This value must - | correspond with one of the application's configured cache stores. + | While using one of the framework's cache driven session backends you may + | list a cache store that should be used for these sessions. This value + | must match with one of the application's configured cache "stores". + | + | Affects: "apc", "dynamodb", "memcached", "redis" | */ - 'store' => null, + 'store' => env('SESSION_STORE'), /* |-------------------------------------------------------------------------- @@ -120,7 +125,10 @@ | */ - 'cookie' => env('SESSION_COOKIE', str_slug(env('APP_NAME', 'pterodactyl'), '_') . '_session'), + 'cookie' => env( + 'SESSION_COOKIE', + Str::slug(env('APP_NAME', 'pterodactyl'), '_') . '_session' + ), /* |-------------------------------------------------------------------------- @@ -155,11 +163,11 @@ | | By setting this option to true, session cookies will only be sent back | to the server if the browser has a HTTPS connection. This will keep - | the cookie from being sent to you if it can not be done securely. + | the cookie from being sent to you when it can't be done securely. | */ - 'secure' => env('SESSION_SECURE_COOKIE', false), + 'secure' => env('SESSION_SECURE_COOKIE'), /* |-------------------------------------------------------------------------- @@ -181,9 +189,9 @@ | | This option determines how your cookies behave when cross-site requests | take place, and can be used to mitigate CSRF attacks. By default, we - | do not enable this as other CSRF protection services are in place. + | will set this value to "lax" since this is a secure default value. | - | Supported: "lax", "strict" + | Supported: "lax", "strict", "none", null | */ diff --git a/config/view.php b/config/view.php index 8796b0abcc..24dea7a4aa 100644 --- a/config/view.php +++ b/config/view.php @@ -27,5 +27,8 @@ | */ - 'compiled' => realpath(storage_path('framework/views')), + 'compiled' => env( + 'VIEW_COMPILED_PATH', + realpath(storage_path('framework/views')) + ), ]; diff --git a/config/vue-i18n-generator.php b/config/vue-i18n-generator.php deleted file mode 100644 index 92a20fd732..0000000000 --- a/config/vue-i18n-generator.php +++ /dev/null @@ -1,50 +0,0 @@ - '/resources/lang', - - /* - |-------------------------------------------------------------------------- - | Laravel translation files - |-------------------------------------------------------------------------- - | - | You can choose which translation files to be generated. - | Note: leave this empty for all the translation files to be generated. - | - */ - - 'langFiles' => [], - - /* - |-------------------------------------------------------------------------- - | Output file - |-------------------------------------------------------------------------- - | - | The javascript path where I will place the generated file. - | Note: the path will be prepended to point to the App directory. - | - */ - 'jsPath' => '/resources/lang/i18n', - 'jsFile' => '/resources/lang/locales.js', - - /* - |-------------------------------------------------------------------------- - | i18n library - |-------------------------------------------------------------------------- - | - | Specify the library you use for localization. - | Options are vue-i18n or vuex-i18n. - | - */ - 'i18nLib' => 'vuex-i18n', -]; diff --git a/database/Factories/AllocationFactory.php b/database/Factories/AllocationFactory.php index c65298ec45..4a5eb70c03 100644 --- a/database/Factories/AllocationFactory.php +++ b/database/Factories/AllocationFactory.php @@ -22,7 +22,7 @@ public function definition(): array { return [ 'ip' => $this->faker->unique()->ipv4, - 'port' => $this->faker->unique()->numberBetween(10000, 20000), + 'port' => $this->faker->unique()->numberBetween(1024, 65535), ]; } diff --git a/database/migrations/2017_03_10_173607_MigrateToNewServiceSystem.php b/database/migrations/2017_03_10_173607_MigrateToNewServiceSystem.php index c786ab4688..7cf5707c44 100644 --- a/database/migrations/2017_03_10_173607_MigrateToNewServiceSystem.php +++ b/database/migrations/2017_03_10_173607_MigrateToNewServiceSystem.php @@ -1,11 +1,5 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ + use Illuminate\Database\Migrations\Migration; class MigrateToNewServiceSystem extends Migration diff --git a/database/migrations/2017_11_11_161922_Add2FaLastAuthorizationTimeColumn.php b/database/migrations/2017_11_11_161922_Add2FaLastAuthorizationTimeColumn.php index 53cb6526b1..b90b150bd4 100644 --- a/database/migrations/2017_11_11_161922_Add2FaLastAuthorizationTimeColumn.php +++ b/database/migrations/2017_11_11_161922_Add2FaLastAuthorizationTimeColumn.php @@ -27,7 +27,7 @@ public function up() DB::table('users')->where('id', $user->id)->update([ 'totp_secret' => Crypt::encrypt($user->totp_secret), - 'updated_at' => Carbon::now()->toIso8601String(), + 'updated_at' => Carbon::now()->toAtomString(), ]); }); }); @@ -46,7 +46,7 @@ public function down() DB::table('users')->where('id', $user->id)->update([ 'totp_secret' => Crypt::decrypt($user->totp_secret), - 'updated_at' => Carbon::now()->toIso8601String(), + 'updated_at' => Carbon::now()->toAtomString(), ]); }); }); diff --git a/phpunit.xml b/phpunit.xml index b3210d5da2..d02831cf4c 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,9 +1,10 @@ - @@ -19,13 +20,12 @@ - - - - - - - - + + + + + + + diff --git a/public/.gitignore b/public/.gitignore index 31c19f1c1d..477f0a1623 100644 --- a/public/.gitignore +++ b/public/.gitignore @@ -1,3 +1,4 @@ +assets assets/* !assets/svgs !assets/svgs/*.svg diff --git a/resources/lang/en/admin/nests.php b/resources/lang/en/admin/nests.php index 19e6b5a1e8..2a6f377077 100644 --- a/resources/lang/en/admin/nests.php +++ b/resources/lang/en/admin/nests.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ return [ 'notices' => [ diff --git a/resources/lang/en/admin/node.php b/resources/lang/en/admin/node.php index 5d59e41ce9..cffd4d7377 100644 --- a/resources/lang/en/admin/node.php +++ b/resources/lang/en/admin/node.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ return [ 'validation' => [ diff --git a/resources/lang/en/admin/server.php b/resources/lang/en/admin/server.php index 6e3ce9f942..29cb72d183 100644 --- a/resources/lang/en/admin/server.php +++ b/resources/lang/en/admin/server.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ return [ 'exceptions' => [ diff --git a/resources/lang/en/admin/user.php b/resources/lang/en/admin/user.php index b7f756cb4f..65e2278060 100644 --- a/resources/lang/en/admin/user.php +++ b/resources/lang/en/admin/user.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ return [ 'exceptions' => [ diff --git a/resources/views/admin/databases/index.blade.php b/resources/views/admin/databases/index.blade.php index df1d13bb76..e4c69c5130 100644 --- a/resources/views/admin/databases/index.blade.php +++ b/resources/views/admin/databases/index.blade.php @@ -1,8 +1,3 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} @extends('layouts.admin') @section('title') diff --git a/resources/views/admin/databases/view.blade.php b/resources/views/admin/databases/view.blade.php index 184d6c72c5..e105751a0c 100644 --- a/resources/views/admin/databases/view.blade.php +++ b/resources/views/admin/databases/view.blade.php @@ -1,8 +1,3 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} @extends('layouts.admin') @section('title') diff --git a/resources/views/admin/eggs/new.blade.php b/resources/views/admin/eggs/new.blade.php index e630eab18b..9a0a07849e 100644 --- a/resources/views/admin/eggs/new.blade.php +++ b/resources/views/admin/eggs/new.blade.php @@ -1,8 +1,3 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} @extends('layouts.admin') @section('title') diff --git a/resources/views/admin/eggs/scripts.blade.php b/resources/views/admin/eggs/scripts.blade.php index 9b82b6d5d1..5bbc9ee3a7 100644 --- a/resources/views/admin/eggs/scripts.blade.php +++ b/resources/views/admin/eggs/scripts.blade.php @@ -1,8 +1,3 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} @extends('layouts.admin') @section('title') diff --git a/resources/views/admin/eggs/variables.blade.php b/resources/views/admin/eggs/variables.blade.php index 2441c725f2..fbc14781ec 100644 --- a/resources/views/admin/eggs/variables.blade.php +++ b/resources/views/admin/eggs/variables.blade.php @@ -1,8 +1,3 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} @extends('layouts.admin') @section('title') diff --git a/resources/views/admin/eggs/view.blade.php b/resources/views/admin/eggs/view.blade.php index f64fde6597..b999d2a910 100644 --- a/resources/views/admin/eggs/view.blade.php +++ b/resources/views/admin/eggs/view.blade.php @@ -1,8 +1,3 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} @extends('layouts.admin') @section('title') diff --git a/resources/views/admin/index.blade.php b/resources/views/admin/index.blade.php index 6c1364a713..ed26a92ab4 100644 --- a/resources/views/admin/index.blade.php +++ b/resources/views/admin/index.blade.php @@ -1,8 +1,3 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} @extends('layouts.admin') @section('title') diff --git a/resources/views/admin/locations/index.blade.php b/resources/views/admin/locations/index.blade.php index 3d5128db2b..c54ffe0faf 100644 --- a/resources/views/admin/locations/index.blade.php +++ b/resources/views/admin/locations/index.blade.php @@ -1,8 +1,3 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} @extends('layouts.admin') @section('title') diff --git a/resources/views/admin/locations/view.blade.php b/resources/views/admin/locations/view.blade.php index 2264ee694c..f34dcf3f5e 100644 --- a/resources/views/admin/locations/view.blade.php +++ b/resources/views/admin/locations/view.blade.php @@ -1,8 +1,3 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} @extends('layouts.admin') @section('title') diff --git a/resources/views/admin/mounts/index.blade.php b/resources/views/admin/mounts/index.blade.php index 067fe30a58..a3b989243e 100644 --- a/resources/views/admin/mounts/index.blade.php +++ b/resources/views/admin/mounts/index.blade.php @@ -1,8 +1,3 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} @extends('layouts.admin') diff --git a/resources/views/admin/mounts/view.blade.php b/resources/views/admin/mounts/view.blade.php index f53007f3fd..96a156d244 100644 --- a/resources/views/admin/mounts/view.blade.php +++ b/resources/views/admin/mounts/view.blade.php @@ -1,8 +1,3 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} @extends('layouts.admin') diff --git a/resources/views/admin/nests/index.blade.php b/resources/views/admin/nests/index.blade.php index b45f1187f1..4bb6ae7847 100644 --- a/resources/views/admin/nests/index.blade.php +++ b/resources/views/admin/nests/index.blade.php @@ -1,8 +1,3 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} @extends('layouts.admin') @section('title') diff --git a/resources/views/admin/nests/new.blade.php b/resources/views/admin/nests/new.blade.php index a93911be72..9a1cb0edc8 100644 --- a/resources/views/admin/nests/new.blade.php +++ b/resources/views/admin/nests/new.blade.php @@ -1,8 +1,3 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} @extends('layouts.admin') @section('title') diff --git a/resources/views/admin/nests/view.blade.php b/resources/views/admin/nests/view.blade.php index 0305ec10f2..9a6730746f 100644 --- a/resources/views/admin/nests/view.blade.php +++ b/resources/views/admin/nests/view.blade.php @@ -1,8 +1,3 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} @extends('layouts.admin') @section('title') diff --git a/resources/views/admin/nodes/index.blade.php b/resources/views/admin/nodes/index.blade.php index 524d811074..a4747b3fcd 100644 --- a/resources/views/admin/nodes/index.blade.php +++ b/resources/views/admin/nodes/index.blade.php @@ -1,8 +1,3 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} @extends('layouts.admin') @section('title') diff --git a/resources/views/admin/nodes/new.blade.php b/resources/views/admin/nodes/new.blade.php index 1dcdca7c95..3e10be5a14 100644 --- a/resources/views/admin/nodes/new.blade.php +++ b/resources/views/admin/nodes/new.blade.php @@ -1,8 +1,3 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} @extends('layouts.admin') @section('title') diff --git a/resources/views/admin/nodes/view/allocation.blade.php b/resources/views/admin/nodes/view/allocation.blade.php index 83ac9c6c60..396428b61b 100644 --- a/resources/views/admin/nodes/view/allocation.blade.php +++ b/resources/views/admin/nodes/view/allocation.blade.php @@ -1,8 +1,3 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} @extends('layouts.admin') @section('title') diff --git a/resources/views/admin/nodes/view/configuration.blade.php b/resources/views/admin/nodes/view/configuration.blade.php index 35e3f5f0a6..1bbf15f337 100644 --- a/resources/views/admin/nodes/view/configuration.blade.php +++ b/resources/views/admin/nodes/view/configuration.blade.php @@ -1,8 +1,3 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} @extends('layouts.admin') @section('title') diff --git a/resources/views/admin/nodes/view/index.blade.php b/resources/views/admin/nodes/view/index.blade.php index 3cfe903848..9ef461076a 100644 --- a/resources/views/admin/nodes/view/index.blade.php +++ b/resources/views/admin/nodes/view/index.blade.php @@ -1,8 +1,3 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} @extends('layouts.admin') @section('title') diff --git a/resources/views/admin/nodes/view/settings.blade.php b/resources/views/admin/nodes/view/settings.blade.php index 7c66cdc75a..e4848aca34 100644 --- a/resources/views/admin/nodes/view/settings.blade.php +++ b/resources/views/admin/nodes/view/settings.blade.php @@ -1,8 +1,3 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} @extends('layouts.admin') @section('title') @@ -242,4 +237,4 @@ }); $('select[name="location_id"]').select2(); -@endsection \ No newline at end of file +@endsection diff --git a/resources/views/admin/servers/index.blade.php b/resources/views/admin/servers/index.blade.php index e152158ce3..4d5ebcdcdf 100644 --- a/resources/views/admin/servers/index.blade.php +++ b/resources/views/admin/servers/index.blade.php @@ -1,8 +1,3 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} @extends('layouts.admin') @section('title') diff --git a/resources/views/admin/servers/new.blade.php b/resources/views/admin/servers/new.blade.php index 51b1b3cca9..bbe779ce6c 100644 --- a/resources/views/admin/servers/new.blade.php +++ b/resources/views/admin/servers/new.blade.php @@ -1,8 +1,3 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} @extends('layouts.admin') @section('title') diff --git a/resources/views/admin/servers/view/build.blade.php b/resources/views/admin/servers/view/build.blade.php index 8add287beb..f7ba9a2d2a 100644 --- a/resources/views/admin/servers/view/build.blade.php +++ b/resources/views/admin/servers/view/build.blade.php @@ -1,8 +1,3 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} @extends('layouts.admin') @section('title') diff --git a/resources/views/admin/servers/view/database.blade.php b/resources/views/admin/servers/view/database.blade.php index 93916584c9..385ce0f3f3 100644 --- a/resources/views/admin/servers/view/database.blade.php +++ b/resources/views/admin/servers/view/database.blade.php @@ -1,8 +1,3 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} @extends('layouts.admin') @section('title') diff --git a/resources/views/admin/servers/view/delete.blade.php b/resources/views/admin/servers/view/delete.blade.php index 2060f2785c..c237096321 100644 --- a/resources/views/admin/servers/view/delete.blade.php +++ b/resources/views/admin/servers/view/delete.blade.php @@ -1,8 +1,3 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} @extends('layouts.admin') @section('title') @@ -77,7 +72,7 @@ $('#deleteform').submit() }); }); - + $('#forcedeletebtn').click(function (event) { event.preventDefault(); swal({ diff --git a/resources/views/admin/servers/view/details.blade.php b/resources/views/admin/servers/view/details.blade.php index a3d6be6283..9b4464b2cc 100644 --- a/resources/views/admin/servers/view/details.blade.php +++ b/resources/views/admin/servers/view/details.blade.php @@ -1,8 +1,3 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} @extends('layouts.admin') @section('title') diff --git a/resources/views/admin/servers/view/index.blade.php b/resources/views/admin/servers/view/index.blade.php index e246b6cd3a..e9b95f9856 100644 --- a/resources/views/admin/servers/view/index.blade.php +++ b/resources/views/admin/servers/view/index.blade.php @@ -1,8 +1,3 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} @extends('layouts.admin') @section('title') diff --git a/resources/views/admin/servers/view/manage.blade.php b/resources/views/admin/servers/view/manage.blade.php index abea741f0c..e6177a43b2 100644 --- a/resources/views/admin/servers/view/manage.blade.php +++ b/resources/views/admin/servers/view/manage.blade.php @@ -1,8 +1,3 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} @extends('layouts.admin') @section('title') diff --git a/resources/views/admin/servers/view/startup.blade.php b/resources/views/admin/servers/view/startup.blade.php index 3dccdfda23..05330298e8 100644 --- a/resources/views/admin/servers/view/startup.blade.php +++ b/resources/views/admin/servers/view/startup.blade.php @@ -1,8 +1,3 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} @extends('layouts.admin') @section('title') diff --git a/resources/views/admin/users/index.blade.php b/resources/views/admin/users/index.blade.php index efde3ba4c0..0c8e906c3d 100644 --- a/resources/views/admin/users/index.blade.php +++ b/resources/views/admin/users/index.blade.php @@ -1,8 +1,3 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} @extends('layouts.admin') @section('title') diff --git a/resources/views/admin/users/new.blade.php b/resources/views/admin/users/new.blade.php index bf006a82da..2ff50164d7 100644 --- a/resources/views/admin/users/new.blade.php +++ b/resources/views/admin/users/new.blade.php @@ -1,8 +1,3 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} @extends('layouts.admin') @section('title') diff --git a/resources/views/admin/users/view.blade.php b/resources/views/admin/users/view.blade.php index d1e6ba9aa6..f042d892d4 100644 --- a/resources/views/admin/users/view.blade.php +++ b/resources/views/admin/users/view.blade.php @@ -1,8 +1,3 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} @extends('layouts.admin') @section('title') diff --git a/resources/views/layouts/admin.blade.php b/resources/views/layouts/admin.blade.php index faadc2c895..1543ddf72e 100644 --- a/resources/views/layouts/admin.blade.php +++ b/resources/views/layouts/admin.blade.php @@ -1,8 +1,3 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} diff --git a/server.php b/server.php deleted file mode 100644 index de038652fe..0000000000 --- a/server.php +++ /dev/null @@ -1,19 +0,0 @@ - - */ -$uri = urldecode( - parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) -); - -// This file allows us to emulate Apache's "mod_rewrite" functionality from the -// built-in PHP web server. This provides a convenient way to test a Laravel -// application without having installed a "real" web server software here. -if ($uri !== '/' && file_exists(__DIR__ . '/public' . $uri)) { - return false; -} - -require_once __DIR__ . '/public/index.php'; diff --git a/tests/Assertions/AssertsActivityLogged.php b/tests/Assertions/AssertsActivityLogged.php index 20571c6b28..7c3232418c 100644 --- a/tests/Assertions/AssertsActivityLogged.php +++ b/tests/Assertions/AssertsActivityLogged.php @@ -31,10 +31,8 @@ public function assertActivityLogged(string $event): void /** * Asserts that a given activity log event was stored with the subjects being * any of the values provided. - * - * @param \Illuminate\Database\Eloquent\Model|array $subjects */ - public function assertActivitySubjects(string $event, $subjects): void + public function assertActivitySubjects(string $event, Model|array $subjects): void { if (is_array($subjects)) { \Webmozart\Assert\Assert::lessThanEq(count(func_get_args()), 2, 'Invalid call to ' . __METHOD__ . ': cannot provide additional arguments if providing an array.'); diff --git a/tests/Assertions/MiddlewareAttributeAssertionsTrait.php b/tests/Assertions/MiddlewareAttributeAssertionsTrait.php index bd5ec0d1de..a74f541ea4 100644 --- a/tests/Assertions/MiddlewareAttributeAssertionsTrait.php +++ b/tests/Assertions/MiddlewareAttributeAssertionsTrait.php @@ -9,7 +9,7 @@ trait MiddlewareAttributeAssertionsTrait /** * Assert a request has an attribute assigned to it. */ - public function assertRequestHasAttribute(string $attribute) + public function assertRequestHasAttribute(string $attribute): void { Assert::assertTrue($this->request->attributes->has($attribute), 'Assert that request mock has ' . $attribute . ' attribute.'); } @@ -17,17 +17,15 @@ public function assertRequestHasAttribute(string $attribute) /** * Assert a request does not have an attribute assigned to it. */ - public function assertRequestMissingAttribute(string $attribute) + public function assertRequestMissingAttribute(string $attribute): void { Assert::assertFalse($this->request->attributes->has($attribute), 'Assert that request mock does not have ' . $attribute . ' attribute.'); } /** * Assert a request attribute matches an expected value. - * - * @param mixed $expected */ - public function assertRequestAttributeEquals($expected, string $attribute) + public function assertRequestAttributeEquals(mixed $expected, string $attribute): void { Assert::assertEquals($expected, $this->request->attributes->get($attribute)); } diff --git a/tests/CreatesApplication.php b/tests/CreatesApplication.php index b7ff2256c8..e9dc91089f 100644 --- a/tests/CreatesApplication.php +++ b/tests/CreatesApplication.php @@ -2,16 +2,15 @@ namespace Pterodactyl\Tests; +use Illuminate\Foundation\Application; use Illuminate\Contracts\Console\Kernel; trait CreatesApplication { /** * Creates the application. - * - * @return \Illuminate\Foundation\Application */ - public function createApplication() + public function createApplication(): Application { $app = require __DIR__ . '/../bootstrap/app.php'; diff --git a/tests/Integration/Api/Application/ApplicationApiIntegrationTestCase.php b/tests/Integration/Api/Application/ApplicationApiIntegrationTestCase.php index bc919cdc3f..73a4f8f51a 100644 --- a/tests/Integration/Api/Application/ApplicationApiIntegrationTestCase.php +++ b/tests/Integration/Api/Application/ApplicationApiIntegrationTestCase.php @@ -20,15 +20,9 @@ abstract class ApplicationApiIntegrationTestCase extends IntegrationTestCase use DatabaseTransactions; use IntegrationJsonRequestAssertions; - /** - * @var \Pterodactyl\Models\ApiKey - */ - private $key; + private ApiKey $key; - /** - * @var \Pterodactyl\Models\User - */ - private $user; + private User $user; /** * Bootstrap application API tests. Creates a default admin user and associated API key diff --git a/tests/Integration/Api/Application/Location/LocationControllerTest.php b/tests/Integration/Api/Application/Location/LocationControllerTest.php index ef3ff3e637..ed75413ac0 100644 --- a/tests/Integration/Api/Application/Location/LocationControllerTest.php +++ b/tests/Integration/Api/Application/Location/LocationControllerTest.php @@ -161,7 +161,7 @@ public function testDeleteLocation() } /** - * Test that all of the defined relationships for a location can be loaded successfully. + * Test that all the defined relationships for a location can be loaded successfully. */ public function testRelationshipsCanBeLoaded() { diff --git a/tests/Integration/Api/Application/Nests/EggControllerTest.php b/tests/Integration/Api/Application/Nests/EggControllerTest.php index c513535f66..07a527e0f0 100644 --- a/tests/Integration/Api/Application/Nests/EggControllerTest.php +++ b/tests/Integration/Api/Application/Nests/EggControllerTest.php @@ -49,7 +49,7 @@ public function testListAllEggsInNest() $this->assertSame( $expected, $actual, - 'Unable to find JSON fragment: ' . PHP_EOL . PHP_EOL . "[{$expected}]" . PHP_EOL . PHP_EOL . 'within' . PHP_EOL . PHP_EOL . "[{$actual}]." + 'Unable to find JSON fragment: ' . PHP_EOL . PHP_EOL . "[$expected]" . PHP_EOL . PHP_EOL . 'within' . PHP_EOL . PHP_EOL . "[$actual]." ); } } @@ -77,7 +77,7 @@ public function testReturnSingleEgg() } /** - * Test that a single egg and all of the defined relationships can be returned. + * Test that a single egg and all the defined relationships can be returned. */ public function testReturnSingleEggWithRelationships() { diff --git a/tests/Integration/Api/Application/Nests/NestControllerTest.php b/tests/Integration/Api/Application/Nests/NestControllerTest.php index b3e1def6d8..799fc18ac7 100644 --- a/tests/Integration/Api/Application/Nests/NestControllerTest.php +++ b/tests/Integration/Api/Application/Nests/NestControllerTest.php @@ -9,10 +9,7 @@ class NestControllerTest extends ApplicationApiIntegrationTestCase { - /** - * @var \Pterodactyl\Contracts\Repository\NestRepositoryInterface - */ - private $repository; + private NestRepositoryInterface $repository; /** * Setup tests. @@ -25,7 +22,7 @@ public function setUp(): void } /** - * Test that the expected nests are returned in the request. + * Test that the expected nests are returned by the request. */ public function testNestResponse() { diff --git a/tests/Integration/Api/Application/Users/UserControllerTest.php b/tests/Integration/Api/Application/Users/UserControllerTest.php index 44a739db25..713f6264ad 100644 --- a/tests/Integration/Api/Application/Users/UserControllerTest.php +++ b/tests/Integration/Api/Application/Users/UserControllerTest.php @@ -55,7 +55,7 @@ public function testGetUsers() 'first_name' => $this->getApiUser()->name_first, 'last_name' => $this->getApiUser()->name_last, 'language' => $this->getApiUser()->language, - 'root_admin' => (bool) $this->getApiUser()->root_admin, + 'root_admin' => $this->getApiUser()->root_admin, '2fa' => (bool) $this->getApiUser()->totp_enabled, 'created_at' => $this->formatTimestamp($this->getApiUser()->created_at), 'updated_at' => $this->formatTimestamp($this->getApiUser()->updated_at), diff --git a/tests/Integration/Api/Client/AccountControllerTest.php b/tests/Integration/Api/Client/AccountControllerTest.php index 6505f43f18..0fe1813a3e 100644 --- a/tests/Integration/Api/Client/AccountControllerTest.php +++ b/tests/Integration/Api/Client/AccountControllerTest.php @@ -52,7 +52,7 @@ public function testEmailIsUpdated() } /** - * Tests that an email is not updated if the password provided in the reuqest is not + * Tests that an email is not updated if the password provided in the request is not * valid for the account. */ public function testEmailIsNotUpdatedWhenPasswordIsInvalid() diff --git a/tests/Integration/Api/Client/ApiKeyControllerTest.php b/tests/Integration/Api/Client/ApiKeyControllerTest.php index 78e662cec9..9aa515f9bb 100644 --- a/tests/Integration/Api/Client/ApiKeyControllerTest.php +++ b/tests/Integration/Api/Client/ApiKeyControllerTest.php @@ -52,7 +52,7 @@ public function testApiKeyCanBeCreatedForAccount(array $data) /** @var \Pterodactyl\Models\User $user */ $user = User::factory()->create(); - // Small sub-test to ensure we're always comparing the number of keys to the + // Small subtest to ensure we're always comparing the number of keys to the // specific logged in account, and not just the total number of keys stored in // the database. ApiKey::factory()->times(10)->create([ @@ -218,7 +218,7 @@ public function testApiKeyBelongingToAnotherUserCannotBeDeleted() } /** - * Tests that an application API key also belonging to the logged in user cannot be + * Tests that an application API key also belonging to the logged-in user cannot be * deleted through this endpoint if it exists. */ public function testApplicationApiKeyCannotBeDeleted() diff --git a/tests/Integration/Api/Client/ClientApiIntegrationTestCase.php b/tests/Integration/Api/Client/ClientApiIntegrationTestCase.php index b7379f4de9..25b5c30cd2 100644 --- a/tests/Integration/Api/Client/ClientApiIntegrationTestCase.php +++ b/tests/Integration/Api/Client/ClientApiIntegrationTestCase.php @@ -7,6 +7,7 @@ use Pterodactyl\Models\Task; use Pterodactyl\Models\User; use InvalidArgumentException; +use Pterodactyl\Models\Model; use Pterodactyl\Models\Backup; use Pterodactyl\Models\Server; use Pterodactyl\Models\Database; @@ -17,6 +18,7 @@ use Pterodactyl\Models\DatabaseHost; use Pterodactyl\Tests\Integration\TestResponse; use Pterodactyl\Tests\Integration\IntegrationTestCase; +use Illuminate\Database\Eloquent\Model as EloquentModel; use Pterodactyl\Transformers\Api\Client\BaseClientTransformer; abstract class ClientApiIntegrationTestCase extends IntegrationTestCase @@ -53,27 +55,24 @@ protected function createTestResponse($response) /** * Returns a link to the specific resource using the client API. - * - * @param mixed $model - * @param string|null $append */ - protected function link($model, $append = null): string + protected function link(mixed $model, string $append = null): string { switch (get_class($model)) { case Server::class: - $link = "/api/client/servers/{$model->uuid}"; + $link = "/api/client/servers/$model->uuid"; break; case Schedule::class: - $link = "/api/client/servers/{$model->server->uuid}/schedules/{$model->id}"; + $link = "/api/client/servers/{$model->server->uuid}/schedules/$model->id"; break; case Task::class: - $link = "/api/client/servers/{$model->schedule->server->uuid}/schedules/{$model->schedule->id}/tasks/{$model->id}"; + $link = "/api/client/servers/{$model->schedule->server->uuid}/schedules/{$model->schedule->id}/tasks/$model->id"; break; case Allocation::class: - $link = "/api/client/servers/{$model->server->uuid}/network/allocations/{$model->id}"; + $link = "/api/client/servers/{$model->server->uuid}/network/allocations/$model->id"; break; case Backup::class: - $link = "/api/client/servers/{$model->server->uuid}/backups/{$model->uuid}"; + $link = "/api/client/servers/{$model->server->uuid}/backups/$model->uuid"; break; default: throw new InvalidArgumentException(sprintf('Cannot create link for Model of type %s', class_basename($model))); @@ -85,10 +84,8 @@ protected function link($model, $append = null): string /** * Asserts that the data passed through matches the output of the data from the transformer. This * will remove the "relationships" key when performing the comparison. - * - * @param \Pterodactyl\Models\Model|\Illuminate\Database\Eloquent\Model $model */ - protected function assertJsonTransformedWith(array $data, $model) + protected function assertJsonTransformedWith(array $data, Model|EloquentModel $model) { $reflect = new ReflectionClass($model); $transformer = sprintf('\\Pterodactyl\\Transformers\\Api\\Client\\%sTransformer', $reflect->getShortName()); diff --git a/tests/Integration/Api/Client/ClientControllerTest.php b/tests/Integration/Api/Client/ClientControllerTest.php index 3ab6b19128..033b893c31 100644 --- a/tests/Integration/Api/Client/ClientControllerTest.php +++ b/tests/Integration/Api/Client/ClientControllerTest.php @@ -11,7 +11,7 @@ class ClientControllerTest extends ClientApiIntegrationTestCase { /** - * Test that only the servers a logged in user is assigned to are returned by the + * Test that only the servers a logged-in user is assigned to are returned by the * API endpoint. Obviously there are cases such as being an administrator or being * a subuser, but for this test we just want to test a basic scenario and pretend * subusers do not exist at all. @@ -241,7 +241,7 @@ public function testOnlyAdminLevelServersAreReturned() ]); // Only servers 2 & 3 (0 indexed) should be returned by the API at this point. The user making - // the request is the owner of server 0, and a subuser of server 1 so they should be exluded. + // the request is the owner of server 0, and a subuser of server 1, so they should be excluded. $response = $this->actingAs($users[0])->getJson('/api/client?type=admin'); $response->assertOk(); @@ -286,10 +286,9 @@ public function testAllServersAreReturnedToAdmin() * Test that no servers get returned if the user requests all admin level servers by using * ?type=admin or ?type=admin-all in the request. * - * @param string $type * @dataProvider filterTypeDataProvider */ - public function testNoServersAreReturnedIfAdminFilterIsPassedByRegularUser($type) + public function testNoServersAreReturnedIfAdminFilterIsPassedByRegularUser(string $type) { /** @var \Pterodactyl\Models\User[] $users */ $users = User::factory()->times(3)->create(); @@ -332,10 +331,7 @@ public function testOnlyPrimaryAllocationIsReturnedToSubuser() $response->assertJsonPath('data.0.attributes.relationships.allocations.data.0.attributes.notes', null); } - /** - * @return array - */ - public function filterTypeDataProvider() + public function filterTypeDataProvider(): array { return [['admin'], ['admin-all']]; } diff --git a/tests/Integration/Api/Client/Server/Allocation/AllocationAuthorizationTest.php b/tests/Integration/Api/Client/Server/Allocation/AllocationAuthorizationTest.php index b698d8c7e0..11ad2fa83d 100644 --- a/tests/Integration/Api/Client/Server/Allocation/AllocationAuthorizationTest.php +++ b/tests/Integration/Api/Client/Server/Allocation/AllocationAuthorizationTest.php @@ -46,9 +46,6 @@ public function testAccessToAServersAllocationsIsRestrictedProperly(string $meth $this->actingAs($user)->json($method, $this->link($server3, '/network/allocations/' . $allocation3->id . $endpoint))->assertNotFound(); } - /** - * @return \string[][] - */ public function methodDataProvider(): array { return [ diff --git a/tests/Integration/Api/Client/Server/Allocation/CreateNewAllocationTest.php b/tests/Integration/Api/Client/Server/Allocation/CreateNewAllocationTest.php index 6b8da4048b..1abb106cd4 100644 --- a/tests/Integration/Api/Client/Server/Allocation/CreateNewAllocationTest.php +++ b/tests/Integration/Api/Client/Server/Allocation/CreateNewAllocationTest.php @@ -72,7 +72,7 @@ public function testAllocationCannotBeCreatedIfNotEnabled() } /** - * Test that an allocation cannot be created if the server has reached it's allocation limit. + * Test that an allocation cannot be created if the server has reached its allocation limit. */ public function testAllocationCannotBeCreatedIfServerIsAtLimit() { @@ -86,10 +86,7 @@ public function testAllocationCannotBeCreatedIfServerIsAtLimit() ->assertJsonPath('errors.0.detail', 'Cannot assign additional allocations to this server: limit has been reached.'); } - /** - * @return array - */ - public function permissionDataProvider() + public function permissionDataProvider(): array { return [[[Permission::ACTION_ALLOCATION_CREATE]], [[]]]; } diff --git a/tests/Integration/Api/Client/Server/Backup/BackupAuthorizationTest.php b/tests/Integration/Api/Client/Server/Backup/BackupAuthorizationTest.php index aaaf38a6fa..187c522a92 100644 --- a/tests/Integration/Api/Client/Server/Backup/BackupAuthorizationTest.php +++ b/tests/Integration/Api/Client/Server/Backup/BackupAuthorizationTest.php @@ -55,9 +55,6 @@ public function testAccessToAServersBackupIsRestrictedProperly(string $method, s $this->actingAs($user)->json($method, $this->link($server3, '/backups/' . $backup3->uuid . $endpoint))->assertNotFound(); } - /** - * @return \string[][] - */ public function methodDataProvider(): array { return [ diff --git a/tests/Integration/Api/Client/Server/Backup/DeleteBackupTest.php b/tests/Integration/Api/Client/Server/Backup/DeleteBackupTest.php index b68b65e885..e5ea95d542 100644 --- a/tests/Integration/Api/Client/Server/Backup/DeleteBackupTest.php +++ b/tests/Integration/Api/Client/Server/Backup/DeleteBackupTest.php @@ -3,6 +3,7 @@ namespace Pterodactyl\Tests\Integration\Api\Client\Server\Backup; use Mockery; +use Mockery\MockInterface; use Illuminate\Http\Response; use Pterodactyl\Models\Backup; use Pterodactyl\Models\Permission; @@ -13,7 +14,7 @@ class DeleteBackupTest extends ClientApiIntegrationTestCase { - private $repository; + private MockInterface $repository; public function setUp(): void { diff --git a/tests/Integration/Api/Client/Server/CommandControllerTest.php b/tests/Integration/Api/Client/Server/CommandControllerTest.php index d6010b675c..8fa106cfc5 100644 --- a/tests/Integration/Api/Client/Server/CommandControllerTest.php +++ b/tests/Integration/Api/Client/Server/CommandControllerTest.php @@ -5,6 +5,7 @@ use Mockery; use GuzzleHttp\Psr7\Request; use Illuminate\Http\Response; +use Pterodactyl\Models\Server; use Pterodactyl\Models\Permission; use GuzzleHttp\Exception\BadResponseException; use GuzzleHttp\Psr7\Response as GuzzleResponse; @@ -14,20 +15,6 @@ class CommandControllerTest extends ClientApiIntegrationTestCase { - /** @var \Mockery\MockInterface */ - private $repository; - - /** - * Setup tests. - */ - public function setUp(): void - { - parent::setUp(); - - $this->repository = Mockery::mock(DaemonCommandRepository::class); - $this->app->instance(DaemonCommandRepository::class, $this->repository); - } - /** * Test that a validation error is returned if there is no command present in the * request. @@ -36,7 +23,7 @@ public function testValidationErrorIsReturnedIfNoCommandIsPresent() { [$user, $server] = $this->generateTestAccount(); - $response = $this->actingAs($user)->postJson("/api/client/servers/{$server->uuid}/command", [ + $response = $this->actingAs($user)->postJson("/api/client/servers/$server->uuid/command", [ 'command' => '', ]); @@ -52,7 +39,7 @@ public function testSubuserWithoutPermissionReceivesError() { [$user, $server] = $this->generateTestAccount([Permission::ACTION_WEBSOCKET_CONNECT]); - $response = $this->actingAs($user)->postJson("/api/client/servers/{$server->uuid}/command", [ + $response = $this->actingAs($user)->postJson("/api/client/servers/$server->uuid/command", [ 'command' => 'say Test', ]); @@ -66,12 +53,14 @@ public function testCommandCanSendToServer() { [$user, $server] = $this->generateTestAccount([Permission::ACTION_CONTROL_CONSOLE]); - $this->repository->expects('setServer')->with(Mockery::on(function ($value) use ($server) { - return $value->uuid === $server->uuid; - }))->andReturnSelf(); - $this->repository->expects('send')->with('say Test')->andReturn(new GuzzleResponse()); + $mock = $this->mock(DaemonCommandRepository::class); + $mock->expects('setServer') + ->with(Mockery::on(fn (Server $value) => $value->is($server))) + ->andReturnSelf(); + + $mock->expects('send')->with('say Test')->andReturn(new GuzzleResponse()); - $response = $this->actingAs($user)->postJson("/api/client/servers/{$server->uuid}/command", [ + $response = $this->actingAs($user)->postJson("/api/client/servers/$server->uuid/command", [ 'command' => 'say Test', ]); @@ -86,13 +75,14 @@ public function testErrorIsReturnedWhenServerIsOffline() { [$user, $server] = $this->generateTestAccount(); - $this->repository->expects('setServer->send')->andThrows( + $mock = $this->mock(DaemonCommandRepository::class); + $mock->expects('setServer->send')->andThrows( new DaemonConnectionException( new BadResponseException('', new Request('GET', 'test'), new GuzzleResponse(Response::HTTP_BAD_GATEWAY)) ) ); - $response = $this->actingAs($user)->postJson("/api/client/servers/{$server->uuid}/command", [ + $response = $this->actingAs($user)->postJson("/api/client/servers/$server->uuid/command", [ 'command' => 'say Test', ]); diff --git a/tests/Integration/Api/Client/Server/Database/DatabaseAuthorizationTest.php b/tests/Integration/Api/Client/Server/Database/DatabaseAuthorizationTest.php index d7063ea336..ba15c595ca 100644 --- a/tests/Integration/Api/Client/Server/Database/DatabaseAuthorizationTest.php +++ b/tests/Integration/Api/Client/Server/Database/DatabaseAuthorizationTest.php @@ -2,7 +2,6 @@ namespace Pterodactyl\Tests\Integration\Api\Client\Server\Database; -use Mockery; use Pterodactyl\Models\Subuser; use Pterodactyl\Models\Database; use Pterodactyl\Models\DatabaseHost; @@ -35,14 +34,10 @@ public function testAccessToAServersDatabasesIsRestrictedProperly(string $method $database2 = Database::factory()->create(['server_id' => $server2->id, 'database_host_id' => $host->id]); $database3 = Database::factory()->create(['server_id' => $server3->id, 'database_host_id' => $host->id]); - $this->instance(DatabasePasswordService::class, $mock = Mockery::mock(DatabasePasswordService::class)); - $this->instance(DatabaseManagementService::class, $mock2 = Mockery::mock(DatabaseManagementService::class)); - - if ($method === 'POST') { - $mock->expects('handle')->andReturnUndefined(); - } else { - $mock2->expects('delete')->andReturnUndefined(); - } + $this + ->mock($method === 'POST' ? DatabasePasswordService::class : DatabaseManagementService::class) + ->expects($method === 'POST' ? 'handle' : 'delete') + ->andReturn($method === 'POST' ? 'foo' : null); $hashids = $this->app->make(HashidsInterface::class); // This is the only valid call for this test, accessing the database for the same @@ -63,9 +58,6 @@ public function testAccessToAServersDatabasesIsRestrictedProperly(string $method $this->actingAs($user)->json($method, $this->link($server3, '/databases/' . $hashids->encode($database3->id) . $endpoint))->assertNotFound(); } - /** - * @return \string[][] - */ public function methodDataProvider(): array { return [ diff --git a/tests/Integration/Api/Client/Server/NetworkAllocationControllerTest.php b/tests/Integration/Api/Client/Server/NetworkAllocationControllerTest.php index 336b4e037b..c9523733d3 100644 --- a/tests/Integration/Api/Client/Server/NetworkAllocationControllerTest.php +++ b/tests/Integration/Api/Client/Server/NetworkAllocationControllerTest.php @@ -133,7 +133,7 @@ public function testPrimaryAllocationCannotBeModifiedByInvalidUser() ->assertForbidden(); } - public function updatePermissionsDataProvider() + public function updatePermissionsDataProvider(): array { return [[[]], [[Permission::ACTION_ALLOCATION_UPDATE]]]; } diff --git a/tests/Integration/Api/Client/Server/PowerControllerTest.php b/tests/Integration/Api/Client/Server/PowerControllerTest.php index 6aed481a85..c061add321 100644 --- a/tests/Integration/Api/Client/Server/PowerControllerTest.php +++ b/tests/Integration/Api/Client/Server/PowerControllerTest.php @@ -16,6 +16,7 @@ class PowerControllerTest extends ClientApiIntegrationTestCase * the command to the server. * * @param string[] $permissions + * * @dataProvider invalidPermissionDataProvider */ public function testSubuserWithoutPermissionsReceivesError(string $action, array $permissions) @@ -23,7 +24,7 @@ public function testSubuserWithoutPermissionsReceivesError(string $action, array [$user, $server] = $this->generateTestAccount($permissions); $this->actingAs($user) - ->postJson("/api/client/servers/{$server->uuid}/power", ['signal' => $action]) + ->postJson("/api/client/servers/$server->uuid/power", ['signal' => $action]) ->assertStatus(Response::HTTP_FORBIDDEN); } @@ -34,7 +35,7 @@ public function testInvalidPowerSignalResultsInError() { [$user, $server] = $this->generateTestAccount(); - $response = $this->actingAs($user)->postJson("/api/client/servers/{$server->uuid}/power", [ + $response = $this->actingAs($user)->postJson("/api/client/servers/$server->uuid/power", [ 'signal' => 'invalid', ]); @@ -65,7 +66,7 @@ public function testActionCanBeSentToServer(string $action, string $permission) ->with(trim($action)); $this->actingAs($user) - ->postJson("/api/client/servers/{$server->uuid}/power", ['signal' => $action]) + ->postJson("/api/client/servers/$server->uuid/power", ['signal' => $action]) ->assertStatus(Response::HTTP_NO_CONTENT); } diff --git a/tests/Integration/Api/Client/Server/ResourceUtilitizationControllerTest.php b/tests/Integration/Api/Client/Server/ResourceUtilizationControllerTest.php similarity index 92% rename from tests/Integration/Api/Client/Server/ResourceUtilitizationControllerTest.php rename to tests/Integration/Api/Client/Server/ResourceUtilizationControllerTest.php index 7c713dd338..b18de2f5f4 100644 --- a/tests/Integration/Api/Client/Server/ResourceUtilitizationControllerTest.php +++ b/tests/Integration/Api/Client/Server/ResourceUtilizationControllerTest.php @@ -7,7 +7,7 @@ use Pterodactyl\Repositories\Wings\DaemonServerRepository; use Pterodactyl\Tests\Integration\Api\Client\ClientApiIntegrationTestCase; -class ResourceUtilitizationControllerTest extends ClientApiIntegrationTestCase +class ResourceUtilizationControllerTest extends ClientApiIntegrationTestCase { /** * Test that the resource utilization for a server is returned in the expected format. @@ -23,7 +23,7 @@ public function testServerResourceUtilizationIsReturned() return $server->uuid === $value->uuid; }))->andReturnSelf()->getMock()->expects('getDetails')->andReturns([]); - $response = $this->actingAs($user)->getJson("/api/client/servers/{$server->uuid}/resources"); + $response = $this->actingAs($user)->getJson("/api/client/servers/$server->uuid/resources"); $response->assertOk(); $response->assertJson([ diff --git a/tests/Integration/Api/Client/Server/Schedule/CreateServerScheduleTest.php b/tests/Integration/Api/Client/Server/Schedule/CreateServerScheduleTest.php index cde99b0902..f87aef2922 100644 --- a/tests/Integration/Api/Client/Server/Schedule/CreateServerScheduleTest.php +++ b/tests/Integration/Api/Client/Server/Schedule/CreateServerScheduleTest.php @@ -12,14 +12,13 @@ class CreateServerScheduleTest extends ClientApiIntegrationTestCase /** * Test that a schedule can be created for the server. * - * @param array $permissions * @dataProvider permissionsDataProvider */ - public function testScheduleCanBeCreatedForServer($permissions) + public function testScheduleCanBeCreatedForServer(array $permissions) { [$user, $server] = $this->generateTestAccount($permissions); - $response = $this->actingAs($user)->postJson("/api/client/servers/{$server->uuid}/schedules", [ + $response = $this->actingAs($user)->postJson("/api/client/servers/$server->uuid/schedules", [ 'name' => 'Test Schedule', 'is_active' => false, 'minute' => '0', @@ -55,17 +54,17 @@ public function testScheduleValidationRules() { [$user, $server] = $this->generateTestAccount(); - $response = $this->actingAs($user)->postJson("/api/client/servers/{$server->uuid}/schedules", []); + $response = $this->actingAs($user)->postJson("/api/client/servers/$server->uuid/schedules", []); $response->assertStatus(Response::HTTP_UNPROCESSABLE_ENTITY); foreach (['name', 'minute', 'hour', 'day_of_month', 'day_of_week'] as $i => $field) { - $response->assertJsonPath("errors.{$i}.code", 'ValidationException'); - $response->assertJsonPath("errors.{$i}.meta.rule", 'required'); - $response->assertJsonPath("errors.{$i}.meta.source_field", $field); + $response->assertJsonPath("errors.$i.code", 'ValidationException'); + $response->assertJsonPath("errors.$i.meta.rule", 'required'); + $response->assertJsonPath("errors.$i.meta.source_field", $field); } $this->actingAs($user) - ->postJson("/api/client/servers/{$server->uuid}/schedules", [ + ->postJson("/api/client/servers/$server->uuid/schedules", [ 'name' => 'Testing', 'is_active' => 'no', 'minute' => '*', @@ -86,7 +85,7 @@ public function testSubuserCannotCreateScheduleWithoutPermissions() [$user, $server] = $this->generateTestAccount([Permission::ACTION_SCHEDULE_UPDATE]); $this->actingAs($user) - ->postJson("/api/client/servers/{$server->uuid}/schedules", []) + ->postJson("/api/client/servers/$server->uuid/schedules", []) ->assertForbidden(); } diff --git a/tests/Integration/Api/Client/Server/Schedule/DeleteServerScheduleTest.php b/tests/Integration/Api/Client/Server/Schedule/DeleteServerScheduleTest.php index f5a2e49dd2..4a79584a82 100644 --- a/tests/Integration/Api/Client/Server/Schedule/DeleteServerScheduleTest.php +++ b/tests/Integration/Api/Client/Server/Schedule/DeleteServerScheduleTest.php @@ -13,10 +13,9 @@ class DeleteServerScheduleTest extends ClientApiIntegrationTestCase /** * Test that a schedule can be deleted from the system. * - * @param array $permissions * @dataProvider permissionsDataProvider */ - public function testScheduleCanBeDeleted($permissions) + public function testScheduleCanBeDeleted(array $permissions) { [$user, $server] = $this->generateTestAccount($permissions); @@ -24,7 +23,7 @@ public function testScheduleCanBeDeleted($permissions) $task = Task::factory()->create(['schedule_id' => $schedule->id]); $this->actingAs($user) - ->deleteJson("/api/client/servers/{$server->uuid}/schedules/{$schedule->id}") + ->deleteJson("/api/client/servers/$server->uuid/schedules/$schedule->id") ->assertStatus(Response::HTTP_NO_CONTENT); $this->assertDatabaseMissing('schedules', ['id' => $schedule->id]); @@ -39,7 +38,7 @@ public function testNotFoundErrorIsReturnedIfScheduleDoesNotExistAtAll() [$user, $server] = $this->generateTestAccount(); $this->actingAs($user) - ->deleteJson("/api/client/servers/{$server->uuid}/schedules/123456789") + ->deleteJson("/api/client/servers/$server->uuid/schedules/123456789") ->assertStatus(Response::HTTP_NOT_FOUND); } @@ -55,7 +54,7 @@ public function testNotFoundErrorIsReturnedIfScheduleDoesNotBelongToServer() $schedule = Schedule::factory()->create(['server_id' => $server2->id]); $this->actingAs($user) - ->deleteJson("/api/client/servers/{$server->uuid}/schedules/{$schedule->id}") + ->deleteJson("/api/client/servers/$server->uuid/schedules/$schedule->id") ->assertStatus(Response::HTTP_NOT_FOUND); $this->assertDatabaseHas('schedules', ['id' => $schedule->id]); @@ -72,7 +71,7 @@ public function testErrorIsReturnedIfSubuserDoesNotHaveRequiredPermissions() $schedule = Schedule::factory()->create(['server_id' => $server->id]); $this->actingAs($user) - ->deleteJson("/api/client/servers/{$server->uuid}/schedules/{$schedule->id}") + ->deleteJson("/api/client/servers/$server->uuid/schedules/$schedule->id") ->assertStatus(Response::HTTP_FORBIDDEN); $this->assertDatabaseHas('schedules', ['id' => $schedule->id]); diff --git a/tests/Integration/Api/Client/Server/Schedule/GetServerSchedulesTest.php b/tests/Integration/Api/Client/Server/Schedule/GetServerSchedulesTest.php index 34830a421a..d9d2ae3f68 100644 --- a/tests/Integration/Api/Client/Server/Schedule/GetServerSchedulesTest.php +++ b/tests/Integration/Api/Client/Server/Schedule/GetServerSchedulesTest.php @@ -23,11 +23,9 @@ protected function tearDown(): void /** * Test that schedules for a server are returned. * - * @param array $permissions - * @param bool $individual * @dataProvider permissionsDataProvider */ - public function testServerSchedulesAreReturned($permissions, $individual) + public function testServerSchedulesAreReturned(array $permissions, bool $individual) { [$user, $server] = $this->generateTestAccount($permissions); @@ -39,8 +37,8 @@ public function testServerSchedulesAreReturned($permissions, $individual) $response = $this->actingAs($user) ->getJson( $individual - ? "/api/client/servers/{$server->uuid}/schedules/{$schedule->id}" - : "/api/client/servers/{$server->uuid}/schedules" + ? "/api/client/servers/$server->uuid/schedules/$schedule->id" + : "/api/client/servers/$server->uuid/schedules" ) ->assertOk(); @@ -69,7 +67,7 @@ public function testScheduleBelongingToAnotherServerCannotBeViewed() $schedule = Schedule::factory()->create(['server_id' => $server2->id]); $this->actingAs($user) - ->getJson("/api/client/servers/{$server->uuid}/schedules/{$schedule->id}") + ->getJson("/api/client/servers/$server->uuid/schedules/$schedule->id") ->assertNotFound(); } @@ -81,13 +79,13 @@ public function testUserWithoutPermissionCannotViewSchedules() [$user, $server] = $this->generateTestAccount([Permission::ACTION_WEBSOCKET_CONNECT]); $this->actingAs($user) - ->getJson("/api/client/servers/{$server->uuid}/schedules") + ->getJson("/api/client/servers/$server->uuid/schedules") ->assertForbidden(); $schedule = Schedule::factory()->create(['server_id' => $server->id]); $this->actingAs($user) - ->getJson("/api/client/servers/{$server->uuid}/schedules/{$schedule->id}") + ->getJson("/api/client/servers/$server->uuid/schedules/$schedule->id") ->assertForbidden(); } diff --git a/tests/Integration/Api/Client/Server/Schedule/ScheduleAuthorizationTest.php b/tests/Integration/Api/Client/Server/Schedule/ScheduleAuthorizationTest.php index 223e9a6737..a8a881194f 100644 --- a/tests/Integration/Api/Client/Server/Schedule/ScheduleAuthorizationTest.php +++ b/tests/Integration/Api/Client/Server/Schedule/ScheduleAuthorizationTest.php @@ -54,9 +54,6 @@ public function testAccessToAServersSchedulesIsRestrictedProperly(string $method $this->actingAs($user)->json($method, $this->link($server3, '/schedules/' . $schedule3->id . $endpoint))->assertNotFound(); } - /** - * @return \string[][] - */ public function methodDataProvider(): array { return [ diff --git a/tests/Integration/Api/Client/Server/Schedule/UpdateServerScheduleTest.php b/tests/Integration/Api/Client/Server/Schedule/UpdateServerScheduleTest.php index f095dd88ff..7442a2b5fa 100644 --- a/tests/Integration/Api/Client/Server/Schedule/UpdateServerScheduleTest.php +++ b/tests/Integration/Api/Client/Server/Schedule/UpdateServerScheduleTest.php @@ -11,10 +11,8 @@ class UpdateServerScheduleTest extends ClientApiIntegrationTestCase { /** * The data to use when updating a schedule. - * - * @var array */ - private $updateData = [ + private array $updateData = [ 'name' => 'Updated Schedule Name', 'minute' => '5', 'hour' => '*', @@ -27,10 +25,9 @@ class UpdateServerScheduleTest extends ClientApiIntegrationTestCase /** * Test that a schedule can be updated. * - * @param array $permissions * @dataProvider permissionsDataProvider */ - public function testScheduleCanBeUpdated($permissions) + public function testScheduleCanBeUpdated(array $permissions) { [$user, $server] = $this->generateTestAccount($permissions); @@ -48,7 +45,7 @@ public function testScheduleCanBeUpdated($permissions) $this->assertFalse($schedule->is_active); $this->assertJsonTransformedWith($response->json('attributes'), $schedule); - $this->assertSame($expected->toIso8601String(), $schedule->next_run_at->toIso8601String()); + $this->assertSame($expected->toAtomString(), $schedule->next_run_at->toAtomString()); } /** diff --git a/tests/Integration/Api/Client/Server/ScheduleTask/CreateServerScheduleTaskTest.php b/tests/Integration/Api/Client/Server/ScheduleTask/CreateServerScheduleTaskTest.php index 81c65b1d2f..a71e0233c9 100644 --- a/tests/Integration/Api/Client/Server/ScheduleTask/CreateServerScheduleTaskTest.php +++ b/tests/Integration/Api/Client/Server/ScheduleTask/CreateServerScheduleTaskTest.php @@ -13,10 +13,9 @@ class CreateServerScheduleTaskTest extends ClientApiIntegrationTestCase /** * Test that a task can be created. * - * @param array $permissions * @dataProvider permissionsDataProvider */ - public function testTaskCanBeCreated($permissions) + public function testTaskCanBeCreated(array $permissions) { [$user, $server] = $this->generateTestAccount($permissions); @@ -56,8 +55,8 @@ public function testValidationErrorsAreReturned() $response = $this->actingAs($user)->postJson($this->link($schedule, '/tasks'))->assertStatus(Response::HTTP_UNPROCESSABLE_ENTITY); foreach (['action', 'payload', 'time_offset'] as $i => $field) { - $response->assertJsonPath("errors.{$i}.meta.rule", $field === 'payload' ? 'required_unless' : 'required'); - $response->assertJsonPath("errors.{$i}.meta.source_field", $field); + $response->assertJsonPath("errors.$i.meta.rule", $field === 'payload' ? 'required_unless' : 'required'); + $response->assertJsonPath("errors.$i.meta.source_field", $field); } $this->actingAs($user)->postJson($this->link($schedule, '/tasks'), [ @@ -151,7 +150,7 @@ public function testErrorIsReturnedIfScheduleDoesNotBelongToServer() $schedule = Schedule::factory()->create(['server_id' => $server2->id]); $this->actingAs($user) - ->postJson("/api/client/servers/{$server->uuid}/schedules/{$schedule->id}/tasks") + ->postJson("/api/client/servers/$server->uuid/schedules/$schedule->id/tasks") ->assertNotFound(); } diff --git a/tests/Integration/Api/Client/Server/ScheduleTask/DeleteScheduleTaskTest.php b/tests/Integration/Api/Client/Server/ScheduleTask/DeleteScheduleTaskTest.php index dc9b3fd42f..b610dd3796 100644 --- a/tests/Integration/Api/Client/Server/ScheduleTask/DeleteScheduleTaskTest.php +++ b/tests/Integration/Api/Client/Server/ScheduleTask/DeleteScheduleTaskTest.php @@ -27,7 +27,7 @@ public function testScheduleNotBelongingToServerReturnsError() /** * Test that an error is returned if the task and schedule in the URL do not line up - * with eachother. + * with each other. */ public function testTaskBelongingToDifferentScheduleReturnsError() { @@ -37,7 +37,7 @@ public function testTaskBelongingToDifferentScheduleReturnsError() $schedule2 = Schedule::factory()->create(['server_id' => $server->id]); $task = Task::factory()->create(['schedule_id' => $schedule->id]); - $this->actingAs($user)->deleteJson("/api/client/servers/{$server->uuid}/schedules/{$schedule2->id}/tasks/{$task->id}")->assertNotFound(); + $this->actingAs($user)->deleteJson("/api/client/servers/$server->uuid/schedules/$schedule2->id/tasks/$task->id")->assertNotFound(); } /** diff --git a/tests/Integration/Api/Client/Server/SettingsControllerTest.php b/tests/Integration/Api/Client/Server/SettingsControllerTest.php index edd4dc5430..c92754cfa1 100644 --- a/tests/Integration/Api/Client/Server/SettingsControllerTest.php +++ b/tests/Integration/Api/Client/Server/SettingsControllerTest.php @@ -14,16 +14,15 @@ class SettingsControllerTest extends ClientApiIntegrationTestCase /** * Test that the server's name can be changed. * - * @param array $permissions * @dataProvider renamePermissionsDataProvider */ - public function testServerNameCanBeChanged($permissions) + public function testServerNameCanBeChanged(array $permissions) { /** @var \Pterodactyl\Models\Server $server */ [$user, $server] = $this->generateTestAccount($permissions); $originalName = $server->name; - $response = $this->actingAs($user)->postJson("/api/client/servers/{$server->uuid}/settings/rename", [ + $response = $this->actingAs($user)->postJson("/api/client/servers/$server->uuid/settings/rename", [ 'name' => '', ]); @@ -34,7 +33,7 @@ public function testServerNameCanBeChanged($permissions) $this->assertSame($originalName, $server->name); $this->actingAs($user) - ->postJson("/api/client/servers/{$server->uuid}/settings/rename", [ + ->postJson("/api/client/servers/$server->uuid/settings/rename", [ 'name' => 'Test Server Name', ]) ->assertStatus(Response::HTTP_NO_CONTENT); @@ -53,7 +52,7 @@ public function testSubuserCannotChangeServerNameWithoutPermission() $originalName = $server->name; $this->actingAs($user) - ->postJson("/api/client/servers/{$server->uuid}/settings/rename", [ + ->postJson("/api/client/servers/$server->uuid/settings/rename", [ 'name' => 'Test Server Name', ]) ->assertStatus(Response::HTTP_FORBIDDEN); @@ -66,10 +65,9 @@ public function testSubuserCannotChangeServerNameWithoutPermission() * Test that a server can be reinstalled. Honestly this test doesn't do much of anything other * than make sure the endpoint works since. * - * @param array $permissions * @dataProvider reinstallPermissionsDataProvider */ - public function testServerCanBeReinstalled($permissions) + public function testServerCanBeReinstalled(array $permissions) { /** @var \Pterodactyl\Models\Server $server */ [$user, $server] = $this->generateTestAccount($permissions); @@ -87,7 +85,7 @@ public function testServerCanBeReinstalled($permissions) ->expects('reinstall') ->andReturnUndefined(); - $this->actingAs($user)->postJson("/api/client/servers/{$server->uuid}/settings/reinstall") + $this->actingAs($user)->postJson("/api/client/servers/$server->uuid/settings/reinstall") ->assertStatus(Response::HTTP_ACCEPTED); $server = $server->refresh(); @@ -103,7 +101,7 @@ public function testSubuserCannotReinstallServerWithoutPermission() [$user, $server] = $this->generateTestAccount([Permission::ACTION_WEBSOCKET_CONNECT]); $this->actingAs($user) - ->postJson("/api/client/servers/{$server->uuid}/settings/reinstall") + ->postJson("/api/client/servers/$server->uuid/settings/reinstall") ->assertStatus(Response::HTTP_FORBIDDEN); $server = $server->refresh(); diff --git a/tests/Integration/Api/Client/Server/Startup/GetStartupAndVariablesTest.php b/tests/Integration/Api/Client/Server/Startup/GetStartupAndVariablesTest.php index 09a6abd029..a8a5b88e03 100644 --- a/tests/Integration/Api/Client/Server/Startup/GetStartupAndVariablesTest.php +++ b/tests/Integration/Api/Client/Server/Startup/GetStartupAndVariablesTest.php @@ -13,16 +13,15 @@ class GetStartupAndVariablesTest extends ClientApiIntegrationTestCase * Test that the startup command and variables are returned for a server, but only the variables * that can be viewed by a user (e.g. user_viewable=true). * - * @param array $permissions * @dataProvider permissionsDataProvider */ - public function testStartupVariablesAreReturnedForServer($permissions) + public function testStartupVariablesAreReturnedForServer(array $permissions) { /** @var \Pterodactyl\Models\Server $server */ [$user, $server] = $this->generateTestAccount($permissions); $egg = $this->cloneEggAndVariables($server->egg); - // BUNGEE_VERSION should never be returned back to the user in this API call, either in + // BUNGEE_VERSION should never be returned to the user in this API call, either in // the array of variables, or revealed in the startup command. $egg->variables()->first()->update([ 'user_viewable' => false, @@ -59,10 +58,7 @@ public function testStartupDataIsNotReturnedWithoutPermission() $this->actingAs($user2)->getJson($this->link($server) . '/startup')->assertNotFound(); } - /** - * @return array[] - */ - public function permissionsDataProvider() + public function permissionsDataProvider(): array { return [[[]], [[Permission::ACTION_STARTUP_READ]]]; } diff --git a/tests/Integration/Api/Client/Server/Startup/UpdateStartupVariableTest.php b/tests/Integration/Api/Client/Server/Startup/UpdateStartupVariableTest.php index 0e5e421c1f..d7ade1eefe 100644 --- a/tests/Integration/Api/Client/Server/Startup/UpdateStartupVariableTest.php +++ b/tests/Integration/Api/Client/Server/Startup/UpdateStartupVariableTest.php @@ -13,10 +13,9 @@ class UpdateStartupVariableTest extends ClientApiIntegrationTestCase /** * Test that a startup variable can be edited successfully for a server. * - * @param array $permissions * @dataProvider permissionsDataProvider */ - public function testStartupVariableCanBeUpdated($permissions) + public function testStartupVariableCanBeUpdated(array $permissions) { /** @var \Pterodactyl\Models\Server $server */ [$user, $server] = $this->generateTestAccount($permissions); @@ -150,10 +149,7 @@ public function testStartupVariableCannotBeUpdatedIfNotUserViewable() $this->actingAs($user2)->putJson($this->link($server) . '/startup/variable')->assertNotFound(); } - /** - * @return \array[][] - */ - public function permissionsDataProvider() + public function permissionsDataProvider(): array { return [[[]], [[Permission::ACTION_STARTUP_UPDATE]]]; } diff --git a/tests/Integration/Api/Client/Server/Subuser/CreateServerSubuserTest.php b/tests/Integration/Api/Client/Server/Subuser/CreateServerSubuserTest.php index 94c9b17ef0..f55ca18a86 100644 --- a/tests/Integration/Api/Client/Server/Subuser/CreateServerSubuserTest.php +++ b/tests/Integration/Api/Client/Server/Subuser/CreateServerSubuserTest.php @@ -17,10 +17,9 @@ class CreateServerSubuserTest extends ClientApiIntegrationTestCase /** * Test that a subuser can be created for a server. * - * @param array $permissions * @dataProvider permissionsDataProvider */ - public function testSubuserCanBeCreated($permissions) + public function testSubuserCanBeCreated(array $permissions) { [$user, $server] = $this->generateTestAccount($permissions); @@ -62,7 +61,7 @@ public function testErrorIsReturnedIfAssigningPermissionsNotAssignedToSelf() ]); $response = $this->actingAs($user)->postJson($this->link($server) . '/users', [ - 'email' => $email = $this->faker->email, + 'email' => $this->faker->email, 'permissions' => [ Permission::ACTION_USER_CREATE, Permission::ACTION_USER_UPDATE, // This permission is not assigned to the subuser. diff --git a/tests/Integration/Api/Client/Server/Subuser/DeleteSubuserTest.php b/tests/Integration/Api/Client/Server/Subuser/DeleteSubuserTest.php index fbcbbc01c7..80fd9845f9 100644 --- a/tests/Integration/Api/Client/Server/Subuser/DeleteSubuserTest.php +++ b/tests/Integration/Api/Client/Server/Subuser/DeleteSubuserTest.php @@ -47,7 +47,7 @@ public function testCorrectSubuserIsDeletedFromServer() $mock->expects('setServer->revokeUserJTI')->with($subuser->id)->andReturnUndefined(); - $this->actingAs($user)->deleteJson($this->link($server) . "/users/{$subuser->uuid}")->assertNoContent(); + $this->actingAs($user)->deleteJson($this->link($server) . "/users/$subuser->uuid")->assertNoContent(); // Try the same test, but this time with a UUID that if cast to an int (shouldn't) line up with // anything in the database. @@ -63,6 +63,6 @@ public function testCorrectSubuserIsDeletedFromServer() $mock->expects('setServer->revokeUserJTI')->with($subuser->id)->andReturnUndefined(); - $this->actingAs($user)->deleteJson($this->link($server) . "/users/{$subuser->uuid}")->assertNoContent(); + $this->actingAs($user)->deleteJson($this->link($server) . "/users/$subuser->uuid")->assertNoContent(); } } diff --git a/tests/Integration/Api/Client/Server/Subuser/SubuserAuthorizationTest.php b/tests/Integration/Api/Client/Server/Subuser/SubuserAuthorizationTest.php index f95e06f72b..242130fe97 100644 --- a/tests/Integration/Api/Client/Server/Subuser/SubuserAuthorizationTest.php +++ b/tests/Integration/Api/Client/Server/Subuser/SubuserAuthorizationTest.php @@ -50,9 +50,6 @@ public function testUserCannotAccessResourceBelongingToOtherServers(string $meth $this->actingAs($user)->json($method, $this->link($server3, '/users/' . $internal->uuid))->assertNotFound(); } - /** - * @return \string[][] - */ public function methodDataProvider(): array { return [['GET'], ['POST'], ['DELETE']]; diff --git a/tests/Integration/Api/Client/Server/WebsocketControllerTest.php b/tests/Integration/Api/Client/Server/WebsocketControllerTest.php index d2656015ea..66bc27af4c 100644 --- a/tests/Integration/Api/Client/Server/WebsocketControllerTest.php +++ b/tests/Integration/Api/Client/Server/WebsocketControllerTest.php @@ -14,14 +14,14 @@ class WebsocketControllerTest extends ClientApiIntegrationTestCase { /** - * Test that a subuser attempting to connect to the websocket recieves an error if they + * Test that a subuser attempting to connect to the websocket receives an error if they * do not explicitly have the permission. */ public function testSubuserWithoutWebsocketPermissionReceivesError() { [$user, $server] = $this->generateTestAccount([Permission::ACTION_CONTROL_RESTART]); - $this->actingAs($user)->getJson("/api/client/servers/{$server->uuid}/websocket") + $this->actingAs($user)->getJson("/api/client/servers/$server->uuid/websocket") ->assertStatus(Response::HTTP_FORBIDDEN) ->assertJsonPath('errors.0.code', 'HttpForbiddenException') ->assertJsonPath('errors.0.detail', 'You do not have permission to connect to this server\'s websocket.'); @@ -33,9 +33,9 @@ public function testSubuserWithoutWebsocketPermissionReceivesError() public function testUserWithoutPermissionForServerReceivesError() { [, $server] = $this->generateTestAccount([Permission::ACTION_WEBSOCKET_CONNECT]); - [$user,] = $this->generateTestAccount([Permission::ACTION_WEBSOCKET_CONNECT]); + [$user] = $this->generateTestAccount([Permission::ACTION_WEBSOCKET_CONNECT]); - $this->actingAs($user)->getJson("/api/client/servers/{$server->uuid}/websocket") + $this->actingAs($user)->getJson("/api/client/servers/$server->uuid/websocket") ->assertStatus(Response::HTTP_NOT_FOUND); } @@ -53,14 +53,14 @@ public function testJwtAndWebsocketUrlAreReturnedForServerOwner() $server->node->scheme = 'https'; $server->node->save(); - $response = $this->actingAs($user)->getJson("/api/client/servers/{$server->uuid}/websocket"); + $response = $this->actingAs($user)->getJson("/api/client/servers/$server->uuid/websocket"); $response->assertOk(); $response->assertJsonStructure(['data' => ['token', 'socket']]); $connection = $response->json('data.socket'); $this->assertStringStartsWith('wss://', $connection, 'Failed asserting that websocket connection address has expected "wss://" prefix.'); - $this->assertStringEndsWith("/api/servers/{$server->uuid}/ws", $connection, 'Failed asserting that websocket connection address uses expected Wings endpoint.'); + $this->assertStringEndsWith("/api/servers/$server->uuid/ws", $connection, 'Failed asserting that websocket connection address uses expected Wings endpoint.'); $config = Configuration::forSymmetricSigner(new Sha256(), $key = InMemory::plainText($server->node->getDecryptedKey())); $config->setValidationConstraints(new SignedWith(new Sha256(), $key)); @@ -102,7 +102,7 @@ public function testJwtIsConfiguredCorrectlyForServerSubuser() /** @var \Pterodactyl\Models\Server $server */ [$user, $server] = $this->generateTestAccount($permissions); - $response = $this->actingAs($user)->getJson("/api/client/servers/{$server->uuid}/websocket"); + $response = $this->actingAs($user)->getJson("/api/client/servers/$server->uuid/websocket"); $response->assertOk(); $response->assertJsonStructure(['data' => ['token', 'socket']]); diff --git a/tests/Integration/Api/Client/TwoFactorControllerTest.php b/tests/Integration/Api/Client/TwoFactorControllerTest.php index 905d67d2bb..6cc086fc7e 100644 --- a/tests/Integration/Api/Client/TwoFactorControllerTest.php +++ b/tests/Integration/Api/Client/TwoFactorControllerTest.php @@ -122,7 +122,7 @@ public function testTwoFactorCanBeEnabledOnAccount() } /** - * Test that two factor authentication can be disabled on an account as long as the password + * Test that two-factor authentication can be disabled on an account as long as the password * provided is valid for the account. */ public function testTwoFactorCanBeDisabledOnAccount() @@ -149,7 +149,7 @@ public function testTwoFactorCanBeDisabledOnAccount() $user = $user->refresh(); $this->assertFalse($user->use_totp); $this->assertNotNull($user->totp_authenticated_at); - $this->assertSame(Carbon::now()->toIso8601String(), $user->totp_authenticated_at->toIso8601String()); + $this->assertSame(Carbon::now()->toAtomString(), $user->totp_authenticated_at->toAtomString()); } /** diff --git a/tests/Integration/Api/Remote/SftpAuthenticationControllerTest.php b/tests/Integration/Api/Remote/SftpAuthenticationControllerTest.php index 4ef485db32..9e7136a809 100644 --- a/tests/Integration/Api/Remote/SftpAuthenticationControllerTest.php +++ b/tests/Integration/Api/Remote/SftpAuthenticationControllerTest.php @@ -97,7 +97,7 @@ public function testPasswordIsValidatedCorrectly() * * @dataProvider authorizationTypeDataProvider */ - public function testUserIsThrottledIfInvalidCredentialsAreProvided(string $type) + public function testUserIsThrottledIfInvalidCredentialsAreProvided() { for ($i = 0; $i <= 10; ++$i) { $this->postJson('/api/remote/sftp/auth', [ diff --git a/tests/Integration/Http/Controllers/Admin/UserControllerTest.php b/tests/Integration/Http/Controllers/Admin/UserControllerTest.php index d101078330..34cf9f938d 100644 --- a/tests/Integration/Http/Controllers/Admin/UserControllerTest.php +++ b/tests/Integration/Http/Controllers/Admin/UserControllerTest.php @@ -21,7 +21,7 @@ class UserControllerTest extends IntegrationTestCase */ public function testIndexReturnsExpectedData() { - $unique = Str::random(16); + $unique = Str::random(); $users = [ User::factory()->create(['username' => $unique . '_1']), User::factory()->create(['username' => $unique . '_2']), @@ -40,7 +40,7 @@ public function testIndexReturnsExpectedData() /** @var \Pterodactyl\Http\Controllers\Admin\UserController $controller */ $controller = $this->app->make(UserController::class); - $request = Request::create('/admin/users?filter[username]=' . $unique, 'GET'); + $request = Request::create('/admin/users?filter[username]=' . $unique); $this->app->instance(Request::class, $request); $data = $controller->index($request)->getData(); diff --git a/tests/Integration/IntegrationTestCase.php b/tests/Integration/IntegrationTestCase.php index 4d504a62a0..103c97b9be 100644 --- a/tests/Integration/IntegrationTestCase.php +++ b/tests/Integration/IntegrationTestCase.php @@ -3,6 +3,7 @@ namespace Pterodactyl\Tests\Integration; use Carbon\CarbonImmutable; +use Carbon\CarbonInterface; use Pterodactyl\Tests\TestCase; use Illuminate\Support\Facades\Event; use Pterodactyl\Events\ActivityLogged; @@ -33,8 +34,8 @@ public function setUp(): void */ protected function formatTimestamp(string $timestamp): string { - return CarbonImmutable::createFromFormat(CarbonImmutable::DEFAULT_TO_STRING_FORMAT, $timestamp) + return CarbonImmutable::createFromFormat(CarbonInterface::DEFAULT_TO_STRING_FORMAT, $timestamp) ->setTimezone(BaseTransformer::RESPONSE_TIMEZONE) - ->toIso8601String(); + ->toAtomString(); } } diff --git a/tests/Integration/Jobs/Schedule/RunTaskJobTest.php b/tests/Integration/Jobs/Schedule/RunTaskJobTest.php index 9a5dfcea90..b80679bd75 100644 --- a/tests/Integration/Jobs/Schedule/RunTaskJobTest.php +++ b/tests/Integration/Jobs/Schedule/RunTaskJobTest.php @@ -48,7 +48,7 @@ public function testInactiveJobIsNotRun() $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)); + $this->assertTrue(CarbonImmutable::now()->isSameAs(DateTimeInterface::ATOM, $schedule->last_run_at)); } public function testJobWithInvalidActionThrowsException() @@ -105,7 +105,7 @@ public function testJobIsExecuted(bool $isManualRun) $this->assertFalse($task->is_queued); $this->assertFalse($schedule->is_processing); - $this->assertTrue(CarbonImmutable::now()->isSameAs(CarbonImmutable::ISO8601, $schedule->last_run_at)); + $this->assertTrue(CarbonImmutable::now()->isSameAs(DateTimeInterface::ATOM, $schedule->last_run_at)); } /** @@ -144,7 +144,7 @@ public function testExceptionDuringRunIsHandledCorrectly(bool $continueOnFailure $this->assertFalse($task->is_queued); $this->assertFalse($schedule->is_processing); - $this->assertTrue(CarbonImmutable::now()->isSameAs(CarbonImmutable::ISO8601, $schedule->last_run_at)); + $this->assertTrue(CarbonImmutable::now()->isSameAs(DateTimeInterface::ATOM, $schedule->last_run_at)); } } @@ -178,10 +178,7 @@ public function testTaskIsNotRunIfServerIsSuspended() $this->assertTrue(Carbon::now()->isSameAs(DateTimeInterface::ATOM, $schedule->last_run_at)); } - /** - * @return array - */ - public function isManualRunDataProvider() + public function isManualRunDataProvider(): array { return [[true], [false]]; } diff --git a/tests/Integration/Services/Allocations/FindAssignableAllocationServiceTest.php b/tests/Integration/Services/Allocations/FindAssignableAllocationServiceTest.php index c3b88e2cf9..3efde3355d 100644 --- a/tests/Integration/Services/Allocations/FindAssignableAllocationServiceTest.php +++ b/tests/Integration/Services/Allocations/FindAssignableAllocationServiceTest.php @@ -25,7 +25,7 @@ public function setUp(): void } /** - * Test that an unassigned allocation is prefered rather than creating an entirely new + * Test that an unassigned allocation is preferred rather than creating an entirely new * allocation for the server. */ public function testExistingAllocationIsPreferred() @@ -141,7 +141,7 @@ public function testExceptionIsThrownIfStartOrEndRangeIsNotNumeric() try { $this->getService()->handle($server); - $this->assertTrue(false, 'This assertion should not be reached.'); + $this->fail('This assertion should not be reached.'); } catch (Exception $exception) { $this->assertInstanceOf(InvalidArgumentException::class, $exception); $this->assertSame('Expected an integerish value. Got: string', $exception->getMessage()); @@ -152,7 +152,7 @@ public function testExceptionIsThrownIfStartOrEndRangeIsNotNumeric() try { $this->getService()->handle($server); - $this->assertTrue(false, 'This assertion should not be reached.'); + $this->fail('This assertion should not be reached.'); } catch (Exception $exception) { $this->assertInstanceOf(InvalidArgumentException::class, $exception); $this->assertSame('Expected an integerish value. Got: string', $exception->getMessage()); @@ -169,10 +169,7 @@ public function testExceptionIsThrownIfFeatureIsNotEnabled() $this->getService()->handle($server); } - /** - * @return \Pterodactyl\Services\Allocations\FindAssignableAllocationService - */ - private function getService() + private function getService(): FindAssignableAllocationService { return $this->app->make(FindAssignableAllocationService::class); } diff --git a/tests/Integration/Services/Backups/DeleteBackupServiceTest.php b/tests/Integration/Services/Backups/DeleteBackupServiceTest.php index 34d4028bd9..d1fb0faac2 100644 --- a/tests/Integration/Services/Backups/DeleteBackupServiceTest.php +++ b/tests/Integration/Services/Backups/DeleteBackupServiceTest.php @@ -2,12 +2,12 @@ namespace Pterodactyl\Tests\Integration\Services\Backups; -use Mockery; use GuzzleHttp\Psr7\Request; use GuzzleHttp\Psr7\Response; use Pterodactyl\Models\Backup; use GuzzleHttp\Exception\ClientException; use Pterodactyl\Extensions\Backups\BackupManager; +use Pterodactyl\Extensions\Filesystem\S3Filesystem; use Pterodactyl\Services\Backups\DeleteBackupService; use Pterodactyl\Tests\Integration\IntegrationTestCase; use Pterodactyl\Repositories\Wings\DaemonBackupRepository; @@ -16,17 +16,6 @@ class DeleteBackupServiceTest extends IntegrationTestCase { - private $repository; - - public function setUp(): void - { - parent::setUp(); - - $this->repository = Mockery::mock(DaemonBackupRepository::class); - - $this->app->instance(DaemonBackupRepository::class, $this->repository); - } - public function testLockedBackupCannotBeDeleted() { $server = $this->createServerModel(); @@ -49,9 +38,8 @@ public function testFailedBackupThatIsLockedCanBeDeleted() 'is_successful' => false, ]); - $this->repository->expects('setServer->delete')->with($backup)->andReturn( - new Response() - ); + $mock = $this->mock(DaemonBackupRepository::class); + $mock->expects('setServer->delete')->with($backup)->andReturn(new Response()); $this->app->make(DeleteBackupService::class)->handle($backup); @@ -65,7 +53,8 @@ public function testExceptionThrownDueToMissingBackupIsIgnored() $server = $this->createServerModel(); $backup = Backup::factory()->create(['server_id' => $server->id]); - $this->repository->expects('setServer->delete')->with($backup)->andThrow( + $mock = $this->mock(DaemonBackupRepository::class); + $mock->expects('setServer->delete')->with($backup)->andThrow( new DaemonConnectionException( new ClientException('', new Request('DELETE', '/'), new Response(404)) ) @@ -83,7 +72,8 @@ public function testExceptionIsThrownIfNot404() $server = $this->createServerModel(); $backup = Backup::factory()->create(['server_id' => $server->id]); - $this->repository->expects('setServer->delete')->with($backup)->andThrow( + $mock = $this->mock(DaemonBackupRepository::class); + $mock->expects('setServer->delete')->with($backup)->andThrow( new DaemonConnectionException( new ClientException('', new Request('DELETE', '/'), new Response(500)) ) @@ -107,17 +97,18 @@ public function testS3ObjectCanBeDeleted() ]); $manager = $this->mock(BackupManager::class); - $manager->expects('getBucket')->andReturns('foobar'); - $manager->expects('adapter')->with(Backup::ADAPTER_AWS_S3)->andReturnSelf(); - $manager->expects('getClient->deleteObject')->with([ + $adapter = $this->mock(S3Filesystem::class); + + $manager->expects('adapter')->with(Backup::ADAPTER_AWS_S3)->andReturn($adapter); + + $adapter->expects('getBucket')->andReturn('foobar'); + $adapter->expects('getClient->deleteObject')->with([ 'Bucket' => 'foobar', 'Key' => sprintf('%s/%s.tar.gz', $server->uuid, $backup->uuid), ]); $this->app->make(DeleteBackupService::class)->handle($backup); - $backup->refresh(); - - $this->assertNotNull($backup->deleted_at); + $this->assertSoftDeleted($backup); } } diff --git a/tests/Integration/Services/Databases/DatabaseManagementServiceTest.php b/tests/Integration/Services/Databases/DatabaseManagementServiceTest.php index e354e02995..72c0e804c9 100644 --- a/tests/Integration/Services/Databases/DatabaseManagementServiceTest.php +++ b/tests/Integration/Services/Databases/DatabaseManagementServiceTest.php @@ -3,6 +3,7 @@ namespace Pterodactyl\Tests\Integration\Services\Databases; use Mockery; +use Mockery\MockInterface; use BadMethodCallException; use InvalidArgumentException; use Pterodactyl\Models\Database; @@ -16,8 +17,7 @@ class DatabaseManagementServiceTest extends IntegrationTestCase { - /** @var \Mockery\MockInterface */ - private $repository; + private MockInterface $repository; /** * Setup tests. @@ -28,8 +28,7 @@ public function setUp(): void config()->set('pterodactyl.client_features.databases.enabled', true); - $this->repository = Mockery::mock(DatabaseRepository::class); - $this->swap(DatabaseRepository::class, $this->repository); + $this->repository = $this->mock(DatabaseRepository::class); } /** @@ -74,10 +73,9 @@ public function testDatabaseCannotBeCreatedIfServerHasReachedLimit() /** * Test that a missing or invalid database name format causes an exception to be thrown. * - * @param array $data * @dataProvider invalidDataDataProvider */ - public function testEmptyDatabaseNameOrInvalidNameTriggersAnException($data) + public function testEmptyDatabaseNameOrInvalidNameTriggersAnException(array $data) { $server = $this->createServerModel(); @@ -166,7 +164,7 @@ public function testServerDatabaseCanBeCreated() $this->assertInstanceOf(Database::class, $response); $this->assertSame($response->server_id, $server->id); - $this->assertMatchesRegularExpression('/^(u[\d]+_)(\w){10}$/', $username); + $this->assertMatchesRegularExpression('/^(u\d+_)(\w){10}$/', $username); $this->assertSame($username, $secondUsername); $this->assertSame(24, strlen($password)); @@ -174,8 +172,8 @@ public function testServerDatabaseCanBeCreated() } /** - * Test that an exception encountered while creating the database leads to cleanup code being called - * and any exceptions encountered while cleaning up go unreported. + * Test that an exception encountered while creating the database leads to the cleanup code + * being called and any exceptions encountered while cleaning up go unreported. */ public function testExceptionEncounteredWhileCreatingDatabaseAttemptsToCleanup() { @@ -211,10 +209,7 @@ public function invalidDataDataProvider(): array ]; } - /** - * @return \Pterodactyl\Services\Databases\DatabaseManagementService - */ - private function getService() + private function getService(): DatabaseManagementService { return $this->app->make(DatabaseManagementService::class); } diff --git a/tests/Integration/Services/Databases/DeployServerDatabaseServiceTest.php b/tests/Integration/Services/Databases/DeployServerDatabaseServiceTest.php index 4d6fa5ec94..0da9245eee 100644 --- a/tests/Integration/Services/Databases/DeployServerDatabaseServiceTest.php +++ b/tests/Integration/Services/Databases/DeployServerDatabaseServiceTest.php @@ -3,6 +3,7 @@ namespace Pterodactyl\Tests\Integration\Services\Databases; use Mockery; +use Mockery\MockInterface; use Pterodactyl\Models\Node; use InvalidArgumentException; use Pterodactyl\Models\Database; @@ -14,8 +15,7 @@ class DeployServerDatabaseServiceTest extends IntegrationTestCase { - /** @var \Mockery\MockInterface */ - private $managementService; + private MockInterface $managementService; /** * Setup tests. @@ -44,10 +44,9 @@ protected function tearDown(): void /** * Test that an error is thrown if either the database name or the remote host are empty. * - * @param array $data * @dataProvider invalidDataProvider */ - public function testErrorIsThrownIfDatabaseNameIsEmpty($data) + public function testErrorIsThrownIfDatabaseNameIsEmpty(array $data) { $server = $this->createServerModel(); @@ -154,10 +153,7 @@ public function invalidDataProvider(): array ]; } - /** - * @return \Pterodactyl\Services\Databases\DeployServerDatabaseService - */ - private function getService() + private function getService(): DeployServerDatabaseService { return $this->app->make(DeployServerDatabaseService::class); } diff --git a/tests/Integration/Services/Deployment/FindViableNodesServiceTest.php b/tests/Integration/Services/Deployment/FindViableNodesServiceTest.php index f32810cc87..f77b5b1446 100644 --- a/tests/Integration/Services/Deployment/FindViableNodesServiceTest.php +++ b/tests/Integration/Services/Deployment/FindViableNodesServiceTest.php @@ -53,7 +53,7 @@ public function testNoExceptionIsThrownIfStringifiedIntegersArePassedForLocation try { $this->getService()->setLocations(['a']); - $this->assertTrue(false, 'This expectation should not be called.'); + $this->fail('This expectation should not be called.'); } catch (Exception $exception) { $this->assertInstanceOf(InvalidArgumentException::class, $exception); $this->assertSame('An array of location IDs should be provided when calling setLocations.', $exception->getMessage()); @@ -61,7 +61,7 @@ public function testNoExceptionIsThrownIfStringifiedIntegersArePassedForLocation try { $this->getService()->setLocations(['1.2', '1', 2]); - $this->assertTrue(false, 'This expectation should not be called.'); + $this->fail('This expectation should not be called.'); } catch (Exception $exception) { $this->assertInstanceOf(InvalidArgumentException::class, $exception); $this->assertSame('An array of location IDs should be provided when calling setLocations.', $exception->getMessage()); @@ -96,7 +96,7 @@ public function testExpectedNodeIsReturnedForLocation() ]), ]; - // Expect that all of the nodes are returned as we're under all of their limits + // Expect that all the nodes are returned as we're under all of their limits // and there is no location filter being provided. $response = $this->getService()->setDisk(512)->setMemory(512)->handle(); $this->assertInstanceOf(Collection::class, $response); @@ -182,10 +182,7 @@ public function testExpectedNodeIsReturnedForLocation() $this->assertSame($nodes[1]->id, $response[0]->id); } - /** - * @return \Pterodactyl\Services\Deployment\FindViableNodesService - */ - private function getService() + private function getService(): FindViableNodesService { return $this->app->make(FindViableNodesService::class); } diff --git a/tests/Integration/Services/Schedules/ProcessScheduleServiceTest.php b/tests/Integration/Services/Schedules/ProcessScheduleServiceTest.php index e5dd9e234e..cd56337ace 100644 --- a/tests/Integration/Services/Schedules/ProcessScheduleServiceTest.php +++ b/tests/Integration/Services/Schedules/ProcessScheduleServiceTest.php @@ -58,10 +58,9 @@ public function testErrorDuringScheduleDataUpdateDoesNotPersistChanges() /** * Test that a job is dispatched as expected using the initial delay. * - * @param bool $now * @dataProvider dispatchNowDataProvider */ - public function testJobCanBeDispatchedWithExpectedInitialDelay($now) + public function testJobCanBeDispatchedWithExpectedInitialDelay(bool $now) { Bus::fake(); @@ -156,10 +155,7 @@ public function dispatchNowDataProvider(): array return [[true], [false]]; } - /** - * @return \Pterodactyl\Services\Schedules\ProcessScheduleService - */ - private function getService() + private function getService(): ProcessScheduleService { return $this->app->make(ProcessScheduleService::class); } diff --git a/tests/Integration/Services/Servers/BuildModificationServiceTest.php b/tests/Integration/Services/Servers/BuildModificationServiceTest.php index cadc9276dd..edebb01256 100644 --- a/tests/Integration/Services/Servers/BuildModificationServiceTest.php +++ b/tests/Integration/Services/Servers/BuildModificationServiceTest.php @@ -3,6 +3,7 @@ namespace Pterodactyl\Tests\Integration\Services\Servers; use Mockery; +use Mockery\MockInterface; use GuzzleHttp\Psr7\Request; use GuzzleHttp\Psr7\Response; use Pterodactyl\Models\Server; @@ -16,8 +17,7 @@ class BuildModificationServiceTest extends IntegrationTestCase { - /** @var \Mockery\MockInterface */ - private $daemonServerRepository; + private MockInterface $daemonServerRepository; /** * Setup tests. @@ -183,7 +183,7 @@ public function testNoExceptionIsThrownIfOnlyRemovingAllocation() /** * Test that allocations in both the add and remove arrays are only added, and not removed. - * This scenario wouldn't really happen in the UI, but it is possible to perform via the API + * This scenario wouldn't really happen in the UI, but it is possible to perform via the API, * so we want to make sure that the logic being used doesn't break if the allocation exists * in both arrays. * @@ -229,7 +229,7 @@ public function testUsingSameAllocationIdMultipleTimesDoesNotError() /** * Test that any changes we made to the server or allocations are rolled back if there is an - * exception while performing any action. This is different than the connection exception + * exception while performing any action. This is different from the connection exception * test which should properly ignore connection issues. We want any other type of exception * to properly be thrown back to the caller. */ @@ -248,10 +248,7 @@ public function testThatUpdatesAreRolledBackIfExceptionIsEncountered() $this->assertDatabaseHas('allocations', ['id' => $allocation->id, 'server_id' => null]); } - /** - * @return \Pterodactyl\Services\Servers\BuildModificationService - */ - private function getService() + private function getService(): BuildModificationService { return $this->app->make(BuildModificationService::class); } diff --git a/tests/Integration/Services/Servers/ServerCreationServiceTest.php b/tests/Integration/Services/Servers/ServerCreationServiceTest.php index c194c21a5e..92a873a189 100644 --- a/tests/Integration/Services/Servers/ServerCreationServiceTest.php +++ b/tests/Integration/Services/Servers/ServerCreationServiceTest.php @@ -3,6 +3,7 @@ namespace Pterodactyl\Tests\Integration\Services\Servers; use Mockery; +use Mockery\MockInterface; use Pterodactyl\Models\Egg; use GuzzleHttp\Psr7\Request; use Pterodactyl\Models\Node; @@ -24,8 +25,7 @@ class ServerCreationServiceTest extends IntegrationTestCase { use WithFaker; - /** @var \Mockery\MockInterface */ - protected $daemonServerRepository; + protected MockInterface $daemonServerRepository; protected Egg $bungeecord; @@ -208,10 +208,7 @@ public function testErrorEncounteredByWingsCausesServerToBeDeleted() $this->assertDatabaseMissing('servers', ['owner_id' => $user->id]); } - /** - * @return \Pterodactyl\Services\Servers\ServerCreationService - */ - private function getService() + private function getService(): ServerCreationService { return $this->app->make(ServerCreationService::class); } diff --git a/tests/Integration/Services/Servers/ServerDeletionServiceTest.php b/tests/Integration/Services/Servers/ServerDeletionServiceTest.php index 197e6b38f8..3bad99932c 100644 --- a/tests/Integration/Services/Servers/ServerDeletionServiceTest.php +++ b/tests/Integration/Services/Servers/ServerDeletionServiceTest.php @@ -4,9 +4,9 @@ use Mockery; use Exception; +use Mockery\MockInterface; use GuzzleHttp\Psr7\Request; use GuzzleHttp\Psr7\Response; -use Pterodactyl\Models\Server; use Pterodactyl\Models\Database; use Pterodactyl\Models\DatabaseHost; use GuzzleHttp\Exception\BadResponseException; @@ -18,13 +18,11 @@ class ServerDeletionServiceTest extends IntegrationTestCase { - /** @var \Mockery\MockInterface */ - private $daemonServerRepository; + private MockInterface $daemonServerRepository; - /** @var \Mockery\MockInterface */ - private $databaseManagementService; + private MockInterface $databaseManagementService; - private static $defaultLogger; + private static ?string $defaultLogger; /** * Stub out services that we don't want to test in here. @@ -102,7 +100,7 @@ public function testForceDeleteIgnoresExceptionFromWings() new DaemonConnectionException(new BadResponseException('Bad request', new Request('GET', '/test'), new Response(500))) ); - $this->getService()->withForce(true)->handle($server); + $this->getService()->withForce()->handle($server); $this->assertDatabaseMissing('servers', ['id' => $server->id]); } @@ -157,10 +155,7 @@ public function testExceptionWhileDeletingDatabasesDoesNotAbortIfForceDeleted() $this->assertDatabaseMissing('databases', ['id' => $db->id]); } - /** - * @return \Pterodactyl\Services\Servers\ServerDeletionService - */ - private function getService() + private function getService(): ServerDeletionService { return $this->app->make(ServerDeletionService::class); } diff --git a/tests/Integration/Services/Servers/StartupModificationServiceTest.php b/tests/Integration/Services/Servers/StartupModificationServiceTest.php index 31a1b85a28..47f4595f0e 100644 --- a/tests/Integration/Services/Servers/StartupModificationServiceTest.php +++ b/tests/Integration/Services/Servers/StartupModificationServiceTest.php @@ -33,7 +33,7 @@ public function testNonAdminCanModifyServerVariables() ], ]); - $this->assertTrue(false, 'This assertion should not be called.'); + $this->fail('This assertion should not be called.'); } catch (Exception $exception) { $this->assertInstanceOf(ValidationException::class, $exception); @@ -161,10 +161,7 @@ public function testInvalidEggIdTriggersException() ->handle($server, ['egg_id' => 123456789]); } - /** - * @return \Pterodactyl\Services\Servers\StartupModificationService - */ - private function getService() + private function getService(): StartupModificationService { return $this->app->make(StartupModificationService::class); } diff --git a/tests/Integration/Services/Servers/SuspensionServiceTest.php b/tests/Integration/Services/Servers/SuspensionServiceTest.php index 258a8ca2ec..bb7297d146 100644 --- a/tests/Integration/Services/Servers/SuspensionServiceTest.php +++ b/tests/Integration/Services/Servers/SuspensionServiceTest.php @@ -3,6 +3,7 @@ namespace Pterodactyl\Tests\Integration\Services\Servers; use Mockery; +use Mockery\MockInterface; use InvalidArgumentException; use Pterodactyl\Models\Server; use Pterodactyl\Services\Servers\SuspensionService; @@ -11,8 +12,7 @@ class SuspensionServiceTest extends IntegrationTestCase { - /** @var \Mockery\MockInterface */ - private $repository; + private MockInterface $repository; /** * Setup test instance. @@ -31,7 +31,7 @@ public function testServerIsSuspendedAndUnsuspended() $this->repository->expects('setServer->sync')->twice()->andReturnSelf(); - $this->getService()->toggle($server, SuspensionService::ACTION_SUSPEND); + $this->getService()->toggle($server); $this->assertTrue($server->refresh()->isSuspended()); @@ -50,7 +50,7 @@ public function testNoActionIsTakenIfSuspensionStatusIsUnchanged() $this->assertFalse($server->isSuspended()); $server->update(['status' => Server::STATUS_SUSPENDED]); - $this->getService()->toggle($server, SuspensionService::ACTION_SUSPEND); + $this->getService()->toggle($server); $server->refresh(); $this->assertTrue($server->isSuspended()); @@ -66,10 +66,7 @@ public function testExceptionIsThrownIfInvalidActionsArePassed() $this->getService()->toggle($server, 'foo'); } - /** - * @return \Pterodactyl\Services\Servers\SuspensionService - */ - private function getService() + private function getService(): SuspensionService { return $this->app->make(SuspensionService::class); } diff --git a/tests/Integration/Services/Servers/VariableValidatorServiceTest.php b/tests/Integration/Services/Servers/VariableValidatorServiceTest.php index 86a7257468..7f0e157fa1 100644 --- a/tests/Integration/Services/Servers/VariableValidatorServiceTest.php +++ b/tests/Integration/Services/Servers/VariableValidatorServiceTest.php @@ -25,7 +25,7 @@ public function setUp(): void } /** - * Test that enviornment variables for a server are validated as expected. + * Test that environment variables for a server are validated as expected. */ public function testEnvironmentVariablesCanBeValidated() { @@ -36,7 +36,7 @@ public function testEnvironmentVariablesCanBeValidated() 'BUNGEE_VERSION' => '1.2.3', ]); - $this->assertTrue(false, 'This statement should not be reached.'); + $this->fail('This statement should not be reached.'); } catch (ValidationException $exception) { $errors = $exception->errors(); @@ -96,7 +96,7 @@ public function testEnvironmentVariablesCanBeUpdatedAsAdmin() 'SERVER_JARFILE' => 'server.jar', ]); - $this->assertTrue(false, 'This statement should not be reached.'); + $this->fail('This statement should not be reached.'); } catch (ValidationException $exception) { $this->assertCount(1, $exception->errors()); $this->assertArrayHasKey('environment.BUNGEE_VERSION', $exception->errors()); @@ -135,10 +135,7 @@ public function testNullableEnvironmentVariablesCanBeUsedCorrectly() $this->assertSame('', $response->get(0)->value); } - /** - * @return \Pterodactyl\Services\Servers\VariableValidatorService - */ - private function getService() + private function getService(): VariableValidatorService { return $this->app->make(VariableValidatorService::class); } diff --git a/tests/Integration/TestResponse.php b/tests/Integration/TestResponse.php index e715071e95..a5a8609c8c 100644 --- a/tests/Integration/TestResponse.php +++ b/tests/Integration/TestResponse.php @@ -12,14 +12,10 @@ class TestResponse extends IlluminateTestResponse { /** * Overrides the default assert status logic to dump out the error to the - * test output if it is caused by a 500 level error and we were not specifically + * test output if it is caused by a 500 level error, and we were not specifically * look for that status response. - * - * @param int $status - * - * @return \Pterodactyl\Tests\Integration\TestResponse */ - public function assertStatus($status) + public function assertStatus($status): TestResponse { $actual = $this->getStatusCode(); @@ -41,10 +37,7 @@ public function assertStatus($status) return $this; } - /** - * @return $this - */ - public function assertForbidden() + public function assertForbidden(): self { return self::assertStatus(Response::HTTP_FORBIDDEN); } diff --git a/tests/Traits/Http/IntegrationJsonRequestAssertions.php b/tests/Traits/Http/IntegrationJsonRequestAssertions.php index 2658520eed..4583d53122 100644 --- a/tests/Traits/Http/IntegrationJsonRequestAssertions.php +++ b/tests/Traits/Http/IntegrationJsonRequestAssertions.php @@ -10,7 +10,7 @@ trait IntegrationJsonRequestAssertions /** * Make assertions about a 404 response on the API. */ - public function assertNotFoundJson(TestResponse $response) + public function assertNotFoundJson(TestResponse $response): void { $response->assertStatus(Response::HTTP_NOT_FOUND); $response->assertJsonStructure(['errors' => [['code', 'status', 'detail']]]); @@ -29,7 +29,7 @@ public function assertNotFoundJson(TestResponse $response) /** * Make assertions about a 403 error returned by the API. */ - public function assertAccessDeniedJson(TestResponse $response) + public function assertAccessDeniedJson(TestResponse $response): void { $response->assertStatus(Response::HTTP_FORBIDDEN); $response->assertJsonStructure(['errors' => [['code', 'status', 'detail']]]); diff --git a/tests/Traits/Http/RequestMockHelpers.php b/tests/Traits/Http/RequestMockHelpers.php index 668d809e65..23e88663c4 100644 --- a/tests/Traits/Http/RequestMockHelpers.php +++ b/tests/Traits/Http/RequestMockHelpers.php @@ -3,6 +3,7 @@ namespace Pterodactyl\Tests\Traits\Http; use Mockery as m; +use Mockery\Mock; use Illuminate\Http\Request; use Pterodactyl\Models\User; use InvalidArgumentException; @@ -10,20 +11,14 @@ trait RequestMockHelpers { - /** - * @var string - */ - private $requestMockClass = Request::class; + private string $requestMockClass = Request::class; - /** - * @var \Illuminate\Http\Request|\Mockery\Mock - */ - protected $request; + protected Request|Mock $request; /** * Set the class to mock for requests. */ - public function setRequestMockClass(string $class) + public function setRequestMockClass(string $class): void { $this->requestMockClass = $class; @@ -33,7 +28,7 @@ public function setRequestMockClass(string $class) /** * Configure the user model that the request mock should return with. */ - public function setRequestUserModel(User $user = null) + public function setRequestUserModel(User $user = null): void { $this->request->shouldReceive('user')->andReturn($user); } @@ -43,6 +38,7 @@ public function setRequestUserModel(User $user = null) */ public function generateRequestUserModel(array $args = []): User { + /** @var \Pterodactyl\Models\User $user */ $user = User::factory()->make($args); $this->setRequestUserModel($user); @@ -51,10 +47,8 @@ public function generateRequestUserModel(array $args = []): User /** * Set a request attribute on the mock object. - * - * @param mixed $value */ - public function setRequestAttribute(string $attribute, $value) + public function setRequestAttribute(string $attribute, mixed $value): void { $this->request->attributes->set($attribute, $value); } @@ -62,7 +56,7 @@ public function setRequestAttribute(string $attribute, $value) /** * Set the request route name. */ - public function setRequestRouteName(string $name) + public function setRequestRouteName(string $name): void { $this->request->shouldReceive('route->getName')->andReturn($name); } @@ -70,7 +64,7 @@ public function setRequestRouteName(string $name) /** * Set the active request object to be an instance of a mocked request. */ - protected function buildRequestMock() + protected function buildRequestMock(): void { $this->request = m::mock($this->requestMockClass); if (!$this->request instanceof Request) { diff --git a/tests/Traits/Integration/CreatesTestModels.php b/tests/Traits/Integration/CreatesTestModels.php index ce64376ce5..62245d72b9 100644 --- a/tests/Traits/Integration/CreatesTestModels.php +++ b/tests/Traits/Integration/CreatesTestModels.php @@ -18,11 +18,9 @@ trait CreatesTestModels * is passed in that normally requires this function to create a model no model will be * created and that attribute's value will be used. * - * The returned server model will have all of the relationships loaded onto it. - * - * @return \Pterodactyl\Models\Server + * The returned server model will have all the relationships loaded onto it. */ - public function createServerModel(array $attributes = []) + public function createServerModel(array $attributes = []): Server { if (isset($attributes['user_id'])) { $attributes['owner_id'] = $attributes['user_id']; @@ -126,11 +124,14 @@ protected function cloneEggAndVariables(Egg $egg): Egg } /** - * Most every test just assumes it is using Bungeecord — this is the critical + * Almost every test just assumes it is using BungeeCord — this is the critical * egg model for all tests unless specified otherwise. */ - private function getBungeecordEgg() + private function getBungeecordEgg(): Egg { - return Egg::query()->where('author', 'support@pterodactyl.io')->where('name', 'Bungeecord')->firstOrFail(); + /** @var \Pterodactyl\Models\Egg $egg */ + $egg = Egg::query()->where('author', 'support@pterodactyl.io')->where('name', 'Bungeecord')->firstOrFail(); + + return $egg; } } diff --git a/tests/Traits/MocksPdoConnection.php b/tests/Traits/MocksPdoConnection.php index c7fe8cdd0a..a938964638 100644 --- a/tests/Traits/MocksPdoConnection.php +++ b/tests/Traits/MocksPdoConnection.php @@ -7,21 +7,17 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Database\MySqlConnection; use Illuminate\Database\ConnectionResolver; +use Illuminate\Database\ConnectionResolverInterface; trait MocksPdoConnection { - /** - * @var \Illuminate\Database\ConnectionResolverInterface|null - */ - private static $initialResolver; + private static ?ConnectionResolverInterface $initialResolver; /** * Generates a mock PDO connection and injects it into the models so that any actual * DB call can be properly intercepted. - * - * @return \Mockery\MockInterface */ - protected function mockPdoConnection() + protected function mockPdoConnection(): Mockery\MockInterface { self::$initialResolver = Model::getConnectionResolver(); @@ -39,7 +35,7 @@ protected function mockPdoConnection() /** * Resets the mock state. */ - protected function tearDownPdoMock() + protected function tearDownPdoMock(): void { if (!self::$initialResolver) { return; diff --git a/tests/Traits/MocksRequestException.php b/tests/Traits/MocksRequestException.php index 69c04913ad..fc011d1529 100644 --- a/tests/Traits/MocksRequestException.php +++ b/tests/Traits/MocksRequestException.php @@ -3,28 +3,21 @@ namespace Pterodactyl\Tests\Traits; use Mockery; +use Mockery\Mock; use Mockery\MockInterface; use GuzzleHttp\Exception\RequestException; trait MocksRequestException { - /** - * @var \GuzzleHttp\Exception\RequestException|\Mockery\Mock - */ - private $exception; + private RequestException|Mock $exception; - /** - * @var mixed - */ - private $exceptionResponse; + private mixed $exceptionResponse; /** * Configure the exception mock to work with the Panel's default exception * handler actions. - * - * @param null $response */ - protected function configureExceptionMock(string $abstract = RequestException::class, $response = null) + protected function configureExceptionMock(string $abstract = RequestException::class, $response = null): void { $this->getExceptionMock($abstract)->shouldReceive('getResponse')->andReturn(value($response)); } diff --git a/tests/Traits/MocksUuids.php b/tests/Traits/MocksUuids.php index 0a84fce126..fb5b7c9715 100644 --- a/tests/Traits/MocksUuids.php +++ b/tests/Traits/MocksUuids.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Tests\Traits; @@ -17,15 +10,13 @@ trait MocksUuids { /** * The known UUID string. - * - * @var string */ - protected $knownUuid = 'ffb5c3a6-ab17-43ab-97f0-8ff37ccd7f5f'; + protected string $knownUuid = 'ffb5c3a6-ab17-43ab-97f0-8ff37ccd7f5f'; /** * Setup a factory mock to produce the same UUID whenever called. */ - public function setKnownUuidFactory() + public function setKnownUuidFactory(): void { $uuid = Uuid::fromString($this->getKnownUuid()); $factoryMock = m::mock(UuidFactory::class . '[uuid4]', [ diff --git a/tests/Unit/Helpers/IsDigitTest.php b/tests/Unit/Helpers/IsDigitTest.php index eae5fbac5a..4ba08042f7 100644 --- a/tests/Unit/Helpers/IsDigitTest.php +++ b/tests/Unit/Helpers/IsDigitTest.php @@ -18,10 +18,8 @@ public function testHelper($value, $response) /** * Provide data to test against the helper function. - * - * @return array */ - public function helperDataProvider() + public function helperDataProvider(): array { return [ [true, false], diff --git a/tests/Unit/Http/Middleware/Api/Application/AuthenticateUserTest.php b/tests/Unit/Http/Middleware/Api/Application/AuthenticateUserTest.php index 8ba4c75fc9..429d63084e 100644 --- a/tests/Unit/Http/Middleware/Api/Application/AuthenticateUserTest.php +++ b/tests/Unit/Http/Middleware/Api/Application/AuthenticateUserTest.php @@ -21,7 +21,7 @@ public function testNoUserDefined() } /** - * Test that a non-admin user results an an exception. + * Test that a non-admin user results in an exception. */ public function testNonAdminUser() { diff --git a/tests/Unit/Http/Middleware/Api/Daemon/DaemonAuthenticateTest.php b/tests/Unit/Http/Middleware/Api/Daemon/DaemonAuthenticateTest.php index 945e08701b..8c8c769203 100644 --- a/tests/Unit/Http/Middleware/Api/Daemon/DaemonAuthenticateTest.php +++ b/tests/Unit/Http/Middleware/Api/Daemon/DaemonAuthenticateTest.php @@ -3,6 +3,7 @@ namespace Pterodactyl\Tests\Unit\Http\Middleware\Api\Daemon; use Mockery as m; +use Mockery\MockInterface; use Pterodactyl\Models\Node; use Illuminate\Contracts\Encryption\Encrypter; use Pterodactyl\Repositories\Eloquent\NodeRepository; @@ -15,15 +16,9 @@ class DaemonAuthenticateTest extends MiddlewareTestCase { - /** - * @var \Mockery\MockInterface - */ - private $repository; + private MockInterface $encrypter; - /** - * @var \Mockery\MockInterface - */ - private $encrypter; + private MockInterface $repository; /** * Setup tests. diff --git a/tests/Unit/Http/Middleware/LanguageMiddlewareTest.php b/tests/Unit/Http/Middleware/LanguageMiddlewareTest.php index 569ef417a2..c870d8e5b4 100644 --- a/tests/Unit/Http/Middleware/LanguageMiddlewareTest.php +++ b/tests/Unit/Http/Middleware/LanguageMiddlewareTest.php @@ -3,16 +3,14 @@ namespace Pterodactyl\Tests\Unit\Http\Middleware; use Mockery as m; +use Mockery\MockInterface; use Pterodactyl\Models\User; use Illuminate\Foundation\Application; use Pterodactyl\Http\Middleware\LanguageMiddleware; class LanguageMiddlewareTest extends MiddlewareTestCase { - /** - * @var \Illuminate\Foundation\Application|\Mockery\Mock - */ - private $appMock; + private MockInterface $appMock; /** * Setup tests. diff --git a/tests/Unit/Http/Middleware/MaintenanceMiddlewareTest.php b/tests/Unit/Http/Middleware/MaintenanceMiddlewareTest.php index a007dd34ad..573c788140 100644 --- a/tests/Unit/Http/Middleware/MaintenanceMiddlewareTest.php +++ b/tests/Unit/Http/Middleware/MaintenanceMiddlewareTest.php @@ -3,6 +3,7 @@ namespace Pterodactyl\Tests\Unit\Http\Middleware; use Mockery as m; +use Mockery\MockInterface; use Pterodactyl\Models\Node; use Illuminate\Http\Response; use Pterodactyl\Models\Server; @@ -11,10 +12,7 @@ class MaintenanceMiddlewareTest extends MiddlewareTestCase { - /** - * @var \Illuminate\Contracts\Routing\ResponseFactory|\Mockery\Mock - */ - private $response; + private MockInterface $response; /** * Setup tests. diff --git a/tests/Unit/Http/Middleware/RedirectIfAuthenticatedTest.php b/tests/Unit/Http/Middleware/RedirectIfAuthenticatedTest.php index ea4bc8014a..68f238aaf9 100644 --- a/tests/Unit/Http/Middleware/RedirectIfAuthenticatedTest.php +++ b/tests/Unit/Http/Middleware/RedirectIfAuthenticatedTest.php @@ -3,16 +3,14 @@ namespace Pterodactyl\Tests\Unit\Http\Middleware; use Mockery as m; +use Mockery\MockInterface; use Illuminate\Auth\AuthManager; use Illuminate\Http\RedirectResponse; use Pterodactyl\Http\Middleware\RedirectIfAuthenticated; class RedirectIfAuthenticatedTest extends MiddlewareTestCase { - /** - * @var \Illuminate\Auth\AuthManager|\Mockery\Mock - */ - private $authManager; + private MockInterface $authManager; /** * Setup tests. From 2828a4b1e049f5e19127297dc40e073a26a8c5e1 Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Fri, 14 Oct 2022 11:00:10 -0600 Subject: [PATCH 247/458] fix AssetManifestService error when loading the panel --- app/Services/Helpers/AssetHashService.php | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/app/Services/Helpers/AssetHashService.php b/app/Services/Helpers/AssetHashService.php index ef29c03a90..2e1c1aaa65 100644 --- a/app/Services/Helpers/AssetHashService.php +++ b/app/Services/Helpers/AssetHashService.php @@ -15,7 +15,7 @@ class AssetHashService private Filesystem $filesystem; - protected static mixed $manifest; + protected static mixed $manifest = null; /** * AssetHashService constructor. @@ -99,9 +99,13 @@ public function js(string $resource): string */ protected function manifest(): array { - return self::$manifest ?: self::$manifest = json_decode( - $this->filesystem->get(self::MANIFEST_PATH), - true - ); + if (static::$manifest === null) { + self::$manifest = json_decode( + $this->filesystem->get(self::MANIFEST_PATH), + true + ); + } + + return static::$manifest; } } From 9c6822f62d18fc686afd5a655fecc0ff679a802f Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Sun, 16 Oct 2022 12:34:36 -0600 Subject: [PATCH 248/458] Update CHANGELOG.md --- CHANGELOG.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 288c7a15e4..cf74334192 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,20 @@ 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. +## [Unreleased] +### Changed +* Changed minimum PHP version is now 8.0 instead of `7.4`. +* Upgraded from Laravel 8 to Laravel 9. + +## v1.10.4 +### Fixed +* Fixed an issue where subusers could be given permissions that are not actually registered or used. +* Fixed an issue where node FQDNs could not just be IP addresses. + +### Changed +* Change maximum number of API keys per user from `10` to `25`. +* Change byte unit prefix from `B` to `iB` to better reflect our usage of base 2 (multiples of 1024). + ## v1.10.3 ### Fixed * S3 Backup driver now supports Cloudflare R2. From d1beb2e1ad72b239b2e9c6671cf244cf91cd0618 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sun, 16 Oct 2022 16:24:49 -0700 Subject: [PATCH 249/458] Fix file verification on upload; just block folders (#4442) --- resources/scripts/components/server/files/UploadButton.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/scripts/components/server/files/UploadButton.tsx b/resources/scripts/components/server/files/UploadButton.tsx index 51225d6997..25277456d1 100644 --- a/resources/scripts/components/server/files/UploadButton.tsx +++ b/resources/scripts/components/server/files/UploadButton.tsx @@ -67,7 +67,7 @@ export default ({ className }: WithClassname) => { const onFileSubmission = (files: FileList) => { clearAndAddHttpError(); const list = Array.from(files); - if (list.some((file) => !file.type && file.size % 4096 === 0)) { + if (list.some((file) => !file.size || (!file.type && file.size === 4096))) { return addError('Folder uploads are not supported at this time.', 'Error'); } From 4dd30fe2f0b12186853029e309b44c3a4d5945de Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Wed, 19 Oct 2022 22:36:58 -0600 Subject: [PATCH 250/458] composer: update lock --- composer.lock | 300 ++++++++++++++++++++++++++------------------------ 1 file changed, 156 insertions(+), 144 deletions(-) diff --git a/composer.lock b/composer.lock index 3b04605863..a506d4edc2 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "8244c912d7f8c69a2a29aa1ee45982cb", + "content-hash": "80a0f1016b1ba9e0b31d6cfe0f27f8c1", "packages": [ { "name": "aws/aws-crt-php", @@ -58,16 +58,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.238.2", + "version": "3.238.6", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "f2a1351b5eb5fe17508cc5c907a786955882b93c" + "reference": "79a76b438bd20ae687394561b4f28cb6c10db08e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/f2a1351b5eb5fe17508cc5c907a786955882b93c", - "reference": "f2a1351b5eb5fe17508cc5c907a786955882b93c", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/79a76b438bd20ae687394561b4f28cb6c10db08e", + "reference": "79a76b438bd20ae687394561b4f28cb6c10db08e", "shasum": "" }, "require": { @@ -146,9 +146,9 @@ "support": { "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.238.2" + "source": "https://github.com/aws/aws-sdk-php/tree/3.238.6" }, - "time": "2022-10-06T18:29:40+00:00" + "time": "2022-10-17T18:17:10+00:00" }, { "name": "brick/math", @@ -530,34 +530,35 @@ }, { "name": "doctrine/event-manager", - "version": "1.1.2", + "version": "1.2.0", "source": { "type": "git", "url": "https://github.com/doctrine/event-manager.git", - "reference": "eb2ecf80e3093e8f3c2769ac838e27d8ede8e683" + "reference": "95aa4cb529f1e96576f3fda9f5705ada4056a520" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/event-manager/zipball/eb2ecf80e3093e8f3c2769ac838e27d8ede8e683", - "reference": "eb2ecf80e3093e8f3c2769ac838e27d8ede8e683", + "url": "https://api.github.com/repos/doctrine/event-manager/zipball/95aa4cb529f1e96576f3fda9f5705ada4056a520", + "reference": "95aa4cb529f1e96576f3fda9f5705ada4056a520", "shasum": "" }, "require": { + "doctrine/deprecations": "^0.5.3 || ^1", "php": "^7.1 || ^8.0" }, "conflict": { "doctrine/common": "<2.9" }, "require-dev": { - "doctrine/coding-standard": "^9", - "phpstan/phpstan": "~1.4.10 || ^1.5.4", + "doctrine/coding-standard": "^9 || ^10", + "phpstan/phpstan": "~1.4.10 || ^1.8.8", "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "vimeo/psalm": "^4.22" + "vimeo/psalm": "^4.24" }, "type": "library", "autoload": { "psr-4": { - "Doctrine\\Common\\": "lib/Doctrine/Common" + "Doctrine\\Common\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -601,7 +602,7 @@ ], "support": { "issues": "https://github.com/doctrine/event-manager/issues", - "source": "https://github.com/doctrine/event-manager/tree/1.1.2" + "source": "https://github.com/doctrine/event-manager/tree/1.2.0" }, "funding": [ { @@ -617,7 +618,7 @@ "type": "tidelift" } ], - "time": "2022-07-27T22:18:11+00:00" + "time": "2022-10-12T20:51:15+00:00" }, { "name": "doctrine/inflector", @@ -1512,16 +1513,16 @@ }, { "name": "laravel/framework", - "version": "v9.34.0", + "version": "v9.36.3", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "b7af7ff35497eb1c4e61652f522a862164dbba5a" + "reference": "80ba0561b3682b96743e1c152fde0698bbdb2412" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/b7af7ff35497eb1c4e61652f522a862164dbba5a", - "reference": "b7af7ff35497eb1c4e61652f522a862164dbba5a", + "url": "https://api.github.com/repos/laravel/framework/zipball/80ba0561b3682b96743e1c152fde0698bbdb2412", + "reference": "80ba0561b3682b96743e1c152fde0698bbdb2412", "shasum": "" }, "require": { @@ -1610,7 +1611,7 @@ "league/flysystem-read-only": "^3.3", "league/flysystem-sftp-v3": "^3.0", "mockery/mockery": "^1.5.1", - "orchestra/testbench-core": "^7.8", + "orchestra/testbench-core": "^7.11", "pda/pheanstalk": "^4.0", "phpstan/phpstan": "^1.4.7", "phpunit/phpunit": "^9.5.8", @@ -1694,7 +1695,7 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2022-10-04T13:33:43+00:00" + "time": "2022-10-19T13:23:53+00:00" }, { "name": "laravel/helpers", @@ -2331,16 +2332,16 @@ }, { "name": "league/flysystem", - "version": "3.5.2", + "version": "3.9.0", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "c73c4eb31f2e883b3897ab5591aa2dbc48112433" + "reference": "60f3760352fe08e918bc3b1acae4e91af092ebe1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/c73c4eb31f2e883b3897ab5591aa2dbc48112433", - "reference": "c73c4eb31f2e883b3897ab5591aa2dbc48112433", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/60f3760352fe08e918bc3b1acae4e91af092ebe1", + "reference": "60f3760352fe08e918bc3b1acae4e91af092ebe1", "shasum": "" }, "require": { @@ -2402,7 +2403,7 @@ ], "support": { "issues": "https://github.com/thephpleague/flysystem/issues", - "source": "https://github.com/thephpleague/flysystem/tree/3.5.2" + "source": "https://github.com/thephpleague/flysystem/tree/3.9.0" }, "funding": [ { @@ -2418,7 +2419,7 @@ "type": "tidelift" } ], - "time": "2022-09-23T18:59:16+00:00" + "time": "2022-10-18T21:02:43+00:00" }, { "name": "league/flysystem-aws-s3-v3", @@ -3197,16 +3198,16 @@ }, { "name": "nunomaduro/termwind", - "version": "v1.14.0", + "version": "v1.14.1", "source": { "type": "git", "url": "https://github.com/nunomaduro/termwind.git", - "reference": "10065367baccf13b6e30f5e9246fa4f63a79eb1d" + "reference": "86fc30eace93b9b6d4c844ba6de76db84184e01b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/10065367baccf13b6e30f5e9246fa4f63a79eb1d", - "reference": "10065367baccf13b6e30f5e9246fa4f63a79eb1d", + "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/86fc30eace93b9b6d4c844ba6de76db84184e01b", + "reference": "86fc30eace93b9b6d4c844ba6de76db84184e01b", "shasum": "" }, "require": { @@ -3263,7 +3264,7 @@ ], "support": { "issues": "https://github.com/nunomaduro/termwind/issues", - "source": "https://github.com/nunomaduro/termwind/tree/v1.14.0" + "source": "https://github.com/nunomaduro/termwind/tree/v1.14.1" }, "funding": [ { @@ -3279,7 +3280,7 @@ "type": "github" } ], - "time": "2022-08-01T11:03:24+00:00" + "time": "2022-10-17T15:20:29+00:00" }, { "name": "paragonie/constant_time_encoding", @@ -3645,16 +3646,16 @@ }, { "name": "predis/predis", - "version": "v2.0.2", + "version": "v2.0.3", "source": { "type": "git", "url": "https://github.com/predis/predis.git", - "reference": "8b5fa928560b48a054fb1fd485fc65f2d8aa9e5c" + "reference": "ff59f745815150c65ed388f7d64e7660fe961771" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/predis/predis/zipball/8b5fa928560b48a054fb1fd485fc65f2d8aa9e5c", - "reference": "8b5fa928560b48a054fb1fd485fc65f2d8aa9e5c", + "url": "https://api.github.com/repos/predis/predis/zipball/ff59f745815150c65ed388f7d64e7660fe961771", + "reference": "ff59f745815150c65ed388f7d64e7660fe961771", "shasum": "" }, "require": { @@ -3704,7 +3705,7 @@ ], "support": { "issues": "https://github.com/predis/predis/issues", - "source": "https://github.com/predis/predis/tree/v2.0.2" + "source": "https://github.com/predis/predis/tree/v2.0.3" }, "funding": [ { @@ -3712,7 +3713,7 @@ "type": "github" } ], - "time": "2022-09-06T14:34:14+00:00" + "time": "2022-10-11T16:52:29+00:00" }, { "name": "prologue/alerts", @@ -4686,16 +4687,16 @@ }, { "name": "spatie/laravel-package-tools", - "version": "1.13.5", + "version": "1.13.6", "source": { "type": "git", "url": "https://github.com/spatie/laravel-package-tools.git", - "reference": "163ee3bc6c0a987535d8a99722a7dbcc5471a140" + "reference": "c377cc7223655c2278c148c1685b8b5a78af5c65" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/163ee3bc6c0a987535d8a99722a7dbcc5471a140", - "reference": "163ee3bc6c0a987535d8a99722a7dbcc5471a140", + "url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/c377cc7223655c2278c148c1685b8b5a78af5c65", + "reference": "c377cc7223655c2278c148c1685b8b5a78af5c65", "shasum": "" }, "require": { @@ -4733,7 +4734,7 @@ ], "support": { "issues": "https://github.com/spatie/laravel-package-tools/issues", - "source": "https://github.com/spatie/laravel-package-tools/tree/1.13.5" + "source": "https://github.com/spatie/laravel-package-tools/tree/1.13.6" }, "funding": [ { @@ -4741,7 +4742,7 @@ "type": "github" } ], - "time": "2022-09-07T14:31:31+00:00" + "time": "2022-10-11T06:37:42+00:00" }, { "name": "spatie/laravel-query-builder", @@ -4918,16 +4919,16 @@ }, { "name": "symfony/console", - "version": "v6.0.13", + "version": "v6.0.14", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "8f14753b865651c2aad107ef97475740a9b0730f" + "reference": "1f89cab8d52c84424f798495b3f10342a7b1a070" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/8f14753b865651c2aad107ef97475740a9b0730f", - "reference": "8f14753b865651c2aad107ef97475740a9b0730f", + "url": "https://api.github.com/repos/symfony/console/zipball/1f89cab8d52c84424f798495b3f10342a7b1a070", + "reference": "1f89cab8d52c84424f798495b3f10342a7b1a070", "shasum": "" }, "require": { @@ -4993,7 +4994,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v6.0.13" + "source": "https://github.com/symfony/console/tree/v6.0.14" }, "funding": [ { @@ -5009,7 +5010,7 @@ "type": "tidelift" } ], - "time": "2022-09-03T14:23:25+00:00" + "time": "2022-10-07T08:02:12+00:00" }, { "name": "symfony/css-selector", @@ -5145,16 +5146,16 @@ }, { "name": "symfony/error-handler", - "version": "v6.0.11", + "version": "v6.0.14", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "cb302377e1b862540436f22be9ff07079a5836ae" + "reference": "81e57c793d9a573f29f8b5296d5d8ee4602badcb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/cb302377e1b862540436f22be9ff07079a5836ae", - "reference": "cb302377e1b862540436f22be9ff07079a5836ae", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/81e57c793d9a573f29f8b5296d5d8ee4602badcb", + "reference": "81e57c793d9a573f29f8b5296d5d8ee4602badcb", "shasum": "" }, "require": { @@ -5196,7 +5197,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v6.0.11" + "source": "https://github.com/symfony/error-handler/tree/v6.0.14" }, "funding": [ { @@ -5212,7 +5213,7 @@ "type": "tidelift" } ], - "time": "2022-07-29T07:39:48+00:00" + "time": "2022-10-07T08:02:12+00:00" }, { "name": "symfony/event-dispatcher", @@ -5439,16 +5440,16 @@ }, { "name": "symfony/http-client", - "version": "v6.0.13", + "version": "v6.0.14", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "2067d3c398d47292f3b413fcc4f56385c1afd0d4" + "reference": "ec183a587e3ad47f03cf1572d4b8437e0fc3e923" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/2067d3c398d47292f3b413fcc4f56385c1afd0d4", - "reference": "2067d3c398d47292f3b413fcc4f56385c1afd0d4", + "url": "https://api.github.com/repos/symfony/http-client/zipball/ec183a587e3ad47f03cf1572d4b8437e0fc3e923", + "reference": "ec183a587e3ad47f03cf1572d4b8437e0fc3e923", "shasum": "" }, "require": { @@ -5503,7 +5504,7 @@ "description": "Provides powerful methods to fetch HTTP resources synchronously or asynchronously", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-client/tree/v6.0.13" + "source": "https://github.com/symfony/http-client/tree/v6.0.14" }, "funding": [ { @@ -5519,7 +5520,7 @@ "type": "tidelift" } ], - "time": "2022-09-09T09:33:56+00:00" + "time": "2022-10-11T15:20:43+00:00" }, { "name": "symfony/http-client-contracts", @@ -5601,16 +5602,16 @@ }, { "name": "symfony/http-foundation", - "version": "v6.0.13", + "version": "v6.0.14", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "294208f37a73b7ae64b4297d936e890d5b514902" + "reference": "e8aa505d35660877e6695d68be53df2ceac7cf57" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/294208f37a73b7ae64b4297d936e890d5b514902", - "reference": "294208f37a73b7ae64b4297d936e890d5b514902", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/e8aa505d35660877e6695d68be53df2ceac7cf57", + "reference": "e8aa505d35660877e6695d68be53df2ceac7cf57", "shasum": "" }, "require": { @@ -5656,7 +5657,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v6.0.13" + "source": "https://github.com/symfony/http-foundation/tree/v6.0.14" }, "funding": [ { @@ -5672,20 +5673,20 @@ "type": "tidelift" } ], - "time": "2022-09-17T07:33:45+00:00" + "time": "2022-10-02T08:16:40+00:00" }, { "name": "symfony/http-kernel", - "version": "v6.0.13", + "version": "v6.0.14", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "5939a039103580d8d86a4c80e245258ad50c91b2" + "reference": "f9fc93c4f12e2fd7dea37f7b5840deb34e9037fc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/5939a039103580d8d86a4c80e245258ad50c91b2", - "reference": "5939a039103580d8d86a4c80e245258ad50c91b2", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/f9fc93c4f12e2fd7dea37f7b5840deb34e9037fc", + "reference": "f9fc93c4f12e2fd7dea37f7b5840deb34e9037fc", "shasum": "" }, "require": { @@ -5765,7 +5766,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v6.0.13" + "source": "https://github.com/symfony/http-kernel/tree/v6.0.14" }, "funding": [ { @@ -5781,7 +5782,7 @@ "type": "tidelift" } ], - "time": "2022-09-30T08:03:37+00:00" + "time": "2022-10-12T07:43:45+00:00" }, { "name": "symfony/mailer", @@ -5924,16 +5925,16 @@ }, { "name": "symfony/mime", - "version": "v6.0.13", + "version": "v6.0.14", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "c1d6eba531d956c23b3127dc6ae6f5ac4a90db6c" + "reference": "c01b88b63418131daf2edd0bdc17fc8a6d1c939a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/c1d6eba531d956c23b3127dc6ae6f5ac4a90db6c", - "reference": "c1d6eba531d956c23b3127dc6ae6f5ac4a90db6c", + "url": "https://api.github.com/repos/symfony/mime/zipball/c01b88b63418131daf2edd0bdc17fc8a6d1c939a", + "reference": "c01b88b63418131daf2edd0bdc17fc8a6d1c939a", "shasum": "" }, "require": { @@ -5945,7 +5946,8 @@ "egulias/email-validator": "~3.0.0", "phpdocumentor/reflection-docblock": "<3.2.2", "phpdocumentor/type-resolver": "<1.4.0", - "symfony/mailer": "<5.4" + "symfony/mailer": "<5.4", + "symfony/serializer": "<5.4.14|>=6.0,<6.0.14|>=6.1,<6.1.6" }, "require-dev": { "egulias/email-validator": "^2.1.10|^3.1", @@ -5953,7 +5955,7 @@ "symfony/dependency-injection": "^5.4|^6.0", "symfony/property-access": "^5.4|^6.0", "symfony/property-info": "^5.4|^6.0", - "symfony/serializer": "^5.4|^6.0" + "symfony/serializer": "^5.4.14|~6.0.14|^6.1.6" }, "type": "library", "autoload": { @@ -5985,7 +5987,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v6.0.13" + "source": "https://github.com/symfony/mime/tree/v6.0.14" }, "funding": [ { @@ -6001,7 +6003,7 @@ "type": "tidelift" } ], - "time": "2022-09-02T08:05:03+00:00" + "time": "2022-10-07T08:02:12+00:00" }, { "name": "symfony/polyfill-ctype", @@ -7106,16 +7108,16 @@ }, { "name": "symfony/string", - "version": "v6.0.13", + "version": "v6.0.14", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "65e99fb179e7241606377e4042cd2161f3dd1c05" + "reference": "3db7da820a6e4a584b714b3933c34c6a7db4d86c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/65e99fb179e7241606377e4042cd2161f3dd1c05", - "reference": "65e99fb179e7241606377e4042cd2161f3dd1c05", + "url": "https://api.github.com/repos/symfony/string/zipball/3db7da820a6e4a584b714b3933c34c6a7db4d86c", + "reference": "3db7da820a6e4a584b714b3933c34c6a7db4d86c", "shasum": "" }, "require": { @@ -7171,7 +7173,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v6.0.13" + "source": "https://github.com/symfony/string/tree/v6.0.14" }, "funding": [ { @@ -7187,20 +7189,20 @@ "type": "tidelift" } ], - "time": "2022-09-02T08:05:03+00:00" + "time": "2022-10-10T09:34:08+00:00" }, { "name": "symfony/translation", - "version": "v6.0.12", + "version": "v6.0.14", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "5e71973b4991e141271465dacf4bf9e719941424" + "reference": "6f99eb179aee4652c0a7cd7c11f2a870d904330c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/5e71973b4991e141271465dacf4bf9e719941424", - "reference": "5e71973b4991e141271465dacf4bf9e719941424", + "url": "https://api.github.com/repos/symfony/translation/zipball/6f99eb179aee4652c0a7cd7c11f2a870d904330c", + "reference": "6f99eb179aee4652c0a7cd7c11f2a870d904330c", "shasum": "" }, "require": { @@ -7266,7 +7268,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v6.0.12" + "source": "https://github.com/symfony/translation/tree/v6.0.14" }, "funding": [ { @@ -7282,7 +7284,7 @@ "type": "tidelift" } ], - "time": "2022-08-02T16:01:06+00:00" + "time": "2022-10-07T08:02:12+00:00" }, { "name": "symfony/translation-contracts", @@ -7438,16 +7440,16 @@ }, { "name": "symfony/var-dumper", - "version": "v6.0.13", + "version": "v6.0.14", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "367522dc769072f2abe554013e073eb079593829" + "reference": "72af925ddd41ca0372d166d004bc38a00c4608cc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/367522dc769072f2abe554013e073eb079593829", - "reference": "367522dc769072f2abe554013e073eb079593829", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/72af925ddd41ca0372d166d004bc38a00c4608cc", + "reference": "72af925ddd41ca0372d166d004bc38a00c4608cc", "shasum": "" }, "require": { @@ -7506,7 +7508,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v6.0.13" + "source": "https://github.com/symfony/var-dumper/tree/v6.0.14" }, "funding": [ { @@ -7522,20 +7524,20 @@ "type": "tidelift" } ], - "time": "2022-09-08T09:32:44+00:00" + "time": "2022-10-07T08:02:12+00:00" }, { "name": "symfony/yaml", - "version": "v5.4.12", + "version": "v5.4.14", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "7a3aa21ac8ab1a96cc6de5bbcab4bc9fc943b18c" + "reference": "e83fe9a72011f07c662da46a05603d66deeeb487" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/7a3aa21ac8ab1a96cc6de5bbcab4bc9fc943b18c", - "reference": "7a3aa21ac8ab1a96cc6de5bbcab4bc9fc943b18c", + "url": "https://api.github.com/repos/symfony/yaml/zipball/e83fe9a72011f07c662da46a05603d66deeeb487", + "reference": "e83fe9a72011f07c662da46a05603d66deeeb487", "shasum": "" }, "require": { @@ -7581,7 +7583,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v5.4.12" + "source": "https://github.com/symfony/yaml/tree/v5.4.14" }, "funding": [ { @@ -7597,7 +7599,7 @@ "type": "tidelift" } ], - "time": "2022-08-02T15:52:22+00:00" + "time": "2022-10-03T15:15:50+00:00" }, { "name": "tijsverkoyen/css-to-inline-styles", @@ -7654,16 +7656,16 @@ }, { "name": "vlucas/phpdotenv", - "version": "v5.4.1", + "version": "v5.5.0", "source": { "type": "git", "url": "https://github.com/vlucas/phpdotenv.git", - "reference": "264dce589e7ce37a7ba99cb901eed8249fbec92f" + "reference": "1a7ea2afc49c3ee6d87061f5a233e3a035d0eae7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/264dce589e7ce37a7ba99cb901eed8249fbec92f", - "reference": "264dce589e7ce37a7ba99cb901eed8249fbec92f", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/1a7ea2afc49c3ee6d87061f5a233e3a035d0eae7", + "reference": "1a7ea2afc49c3ee6d87061f5a233e3a035d0eae7", "shasum": "" }, "require": { @@ -7678,15 +7680,19 @@ "require-dev": { "bamarni/composer-bin-plugin": "^1.4.1", "ext-filter": "*", - "phpunit/phpunit": "^7.5.20 || ^8.5.21 || ^9.5.10" + "phpunit/phpunit": "^7.5.20 || ^8.5.30 || ^9.5.25" }, "suggest": { "ext-filter": "Required to use the boolean validator." }, "type": "library", "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": true + }, "branch-alias": { - "dev-master": "5.4-dev" + "dev-master": "5.5-dev" } }, "autoload": { @@ -7718,7 +7724,7 @@ ], "support": { "issues": "https://github.com/vlucas/phpdotenv/issues", - "source": "https://github.com/vlucas/phpdotenv/tree/v5.4.1" + "source": "https://github.com/vlucas/phpdotenv/tree/v5.5.0" }, "funding": [ { @@ -7730,7 +7736,7 @@ "type": "tidelift" } ], - "time": "2021-12-12T23:22:04+00:00" + "time": "2022-10-16T01:01:54+00:00" }, { "name": "voku/portable-ascii", @@ -8513,16 +8519,16 @@ }, { "name": "friendsofphp/php-cs-fixer", - "version": "v3.11.0", + "version": "v3.12.0", "source": { "type": "git", "url": "https://github.com/FriendsOfPHP/PHP-CS-Fixer.git", - "reference": "7dcdea3f2f5f473464e835be9be55283ff8cfdc3" + "reference": "eae11d945e2885d86e1c080eec1bb30a2aa27998" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/7dcdea3f2f5f473464e835be9be55283ff8cfdc3", - "reference": "7dcdea3f2f5f473464e835be9be55283ff8cfdc3", + "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/eae11d945e2885d86e1c080eec1bb30a2aa27998", + "reference": "eae11d945e2885d86e1c080eec1bb30a2aa27998", "shasum": "" }, "require": { @@ -8555,8 +8561,8 @@ "phpspec/prophecy": "^1.15", "phpspec/prophecy-phpunit": "^2.0", "phpunit/phpunit": "^9.5", - "phpunitgoodpractices/polyfill": "^1.5", - "phpunitgoodpractices/traits": "^1.9.1", + "phpunitgoodpractices/polyfill": "^1.6", + "phpunitgoodpractices/traits": "^1.9.2", "symfony/phpunit-bridge": "^6.0", "symfony/yaml": "^5.4 || ^6.0" }, @@ -8590,7 +8596,7 @@ "description": "A tool to automatically fix PHP code style", "support": { "issues": "https://github.com/FriendsOfPHP/PHP-CS-Fixer/issues", - "source": "https://github.com/FriendsOfPHP/PHP-CS-Fixer/tree/v3.11.0" + "source": "https://github.com/FriendsOfPHP/PHP-CS-Fixer/tree/v3.12.0" }, "funding": [ { @@ -8598,7 +8604,7 @@ "type": "github" } ], - "time": "2022-09-01T18:24:51+00:00" + "time": "2022-10-12T14:20:51+00:00" }, { "name": "hamcrest/hamcrest-php", @@ -8653,16 +8659,16 @@ }, { "name": "itsgoingd/clockwork", - "version": "v5.1.8", + "version": "v5.1.10", "source": { "type": "git", "url": "https://github.com/itsgoingd/clockwork.git", - "reference": "74ee05a61296aa7298164ef5346f0a568aa6106e" + "reference": "8ceb38246028483750573c05dc4f9c17ddb65b7b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/itsgoingd/clockwork/zipball/74ee05a61296aa7298164ef5346f0a568aa6106e", - "reference": "74ee05a61296aa7298164ef5346f0a568aa6106e", + "url": "https://api.github.com/repos/itsgoingd/clockwork/zipball/8ceb38246028483750573c05dc4f9c17ddb65b7b", + "reference": "8ceb38246028483750573c05dc4f9c17ddb65b7b", "shasum": "" }, "require": { @@ -8709,7 +8715,7 @@ ], "support": { "issues": "https://github.com/itsgoingd/clockwork/issues", - "source": "https://github.com/itsgoingd/clockwork/tree/v5.1.8" + "source": "https://github.com/itsgoingd/clockwork/tree/v5.1.10" }, "funding": [ { @@ -8717,7 +8723,7 @@ "type": "github" } ], - "time": "2022-09-25T20:21:14+00:00" + "time": "2022-10-19T21:46:50+00:00" }, { "name": "mockery/mockery", @@ -9303,25 +9309,30 @@ }, { "name": "phpdocumentor/type-resolver", - "version": "1.6.1", + "version": "1.6.2", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "77a32518733312af16a44300404e945338981de3" + "reference": "48f445a408c131e38cab1c235aa6d2bb7a0bb20d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/77a32518733312af16a44300404e945338981de3", - "reference": "77a32518733312af16a44300404e945338981de3", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/48f445a408c131e38cab1c235aa6d2bb7a0bb20d", + "reference": "48f445a408c131e38cab1c235aa6d2bb7a0bb20d", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0", + "php": "^7.4 || ^8.0", "phpdocumentor/reflection-common": "^2.0" }, "require-dev": { "ext-tokenizer": "*", - "psalm/phar": "^4.8" + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-phpunit": "^1.1", + "phpunit/phpunit": "^9.5", + "rector/rector": "^0.13.9", + "vimeo/psalm": "^4.25" }, "type": "library", "extra": { @@ -9347,9 +9358,9 @@ "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", "support": { "issues": "https://github.com/phpDocumentor/TypeResolver/issues", - "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.6.1" + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.6.2" }, - "time": "2022-03-15T21:29:03+00:00" + "time": "2022-10-14T12:47:21+00:00" }, { "name": "phpunit/php-code-coverage", @@ -10943,16 +10954,16 @@ }, { "name": "spatie/laravel-ignition", - "version": "1.5.1", + "version": "1.5.2", "source": { "type": "git", "url": "https://github.com/spatie/laravel-ignition.git", - "reference": "75d465ec577abb432af1ca9b33683d5a6e921eb9" + "reference": "f2336fc79d99aab5cf27fa4aebe5e9c9ecf3808a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-ignition/zipball/75d465ec577abb432af1ca9b33683d5a6e921eb9", - "reference": "75d465ec577abb432af1ca9b33683d5a6e921eb9", + "url": "https://api.github.com/repos/spatie/laravel-ignition/zipball/f2336fc79d99aab5cf27fa4aebe5e9c9ecf3808a", + "reference": "f2336fc79d99aab5cf27fa4aebe5e9c9ecf3808a", "shasum": "" }, "require": { @@ -11029,7 +11040,7 @@ "type": "github" } ], - "time": "2022-10-04T10:14:31+00:00" + "time": "2022-10-14T12:24:21+00:00" }, { "name": "symfony/filesystem", @@ -11285,6 +11296,7 @@ "ext-mbstring": "*", "ext-pdo": "*", "ext-pdo_mysql": "*", + "ext-posix": "*", "ext-zip": "*" }, "platform-dev": [], From 5331fd2cdb884428cd2625363e870ffc96016dd0 Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Thu, 20 Oct 2022 14:32:23 -0600 Subject: [PATCH 251/458] ci(docker): build local code checkout, add caching to dev build --- .github/workflows/docker.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 45f5843516..1f7a44d5a6 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -40,6 +40,8 @@ jobs: uses: docker/build-push-action@v2 if: "contains(github.ref, 'release/v')" with: + context: . + file: ./Dockerfile push: true platforms: linux/amd64,linux/arm64 tags: ${{ steps.docker_meta.outputs.tags }} @@ -49,7 +51,11 @@ jobs: uses: docker/build-push-action@v2 if: "contains(github.ref, 'develop')" with: + context: . + file: ./Dockerfile push: ${{ github.event_name != 'pull_request' }} platforms: linux/amd64,linux/arm64 tags: ${{ steps.docker_meta.outputs.tags }} labels: ${{ steps.docker_meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max From 860b2d890ba597fd7924293349c3536fbf36aca3 Mon Sep 17 00:00:00 2001 From: Lance Pioch Date: Fri, 21 Oct 2022 13:31:59 -0400 Subject: [PATCH 252/458] Fix `php artisan up` (#4457) --- app/Console/Commands/Overrides/UpCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Console/Commands/Overrides/UpCommand.php b/app/Console/Commands/Overrides/UpCommand.php index 0a7caaeb77..225634dc21 100644 --- a/app/Console/Commands/Overrides/UpCommand.php +++ b/app/Console/Commands/Overrides/UpCommand.php @@ -21,6 +21,6 @@ public function handle(): int return 1; } - return parent::handle(); + return parent::handle() ?? 0; } } From 7266c66ebf76877436a1288f1dd57343df497fc8 Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Sun, 23 Oct 2022 18:14:50 -0600 Subject: [PATCH 253/458] un-type getRulesForUpdate; fixes #4463 --- app/Models/Model.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Models/Model.php b/app/Models/Model.php index 5b028c67c0..c5bc666d15 100644 --- a/app/Models/Model.php +++ b/app/Models/Model.php @@ -117,7 +117,7 @@ public static function getRulesForField(string $field): array * Returns the rules associated with the model, specifically for updating the given model * rather than just creating it. */ - public static function getRulesForUpdate(IlluminateModel|int|string $model, string $column = 'id'): array + public static function getRulesForUpdate($model, string $column = 'id'): array { if ($model instanceof Model) { [$id, $column] = [$model->getKey(), $model->getKeyName()]; From e49ba657094fc009387b567fe5d8c8a97f4a40ee Mon Sep 17 00:00:00 2001 From: Lance Pioch Date: Sun, 23 Oct 2022 20:51:20 -0400 Subject: [PATCH 254/458] Fix config key names (#4464) --- .../Environment/EmailSettingsCommand.php | 8 +++--- app/Console/Commands/InfoCommand.php | 11 ++++---- .../Admin/Settings/MailController.php | 4 +-- app/Http/Controllers/Admin/UserController.php | 3 +- .../Requests/Admin/NewUserFormRequest.php | 28 +++++++++++++++++++ app/Providers/SettingsServiceProvider.php | 2 +- resources/views/admin/settings/mail.blade.php | 8 +++--- 7 files changed, 47 insertions(+), 17 deletions(-) create mode 100644 app/Http/Requests/Admin/NewUserFormRequest.php diff --git a/app/Console/Commands/Environment/EmailSettingsCommand.php b/app/Console/Commands/Environment/EmailSettingsCommand.php index cae0094c0e..1fd9a2aeef 100644 --- a/app/Console/Commands/Environment/EmailSettingsCommand.php +++ b/app/Console/Commands/Environment/EmailSettingsCommand.php @@ -49,7 +49,7 @@ public function handle() 'mandrill' => 'Mandrill Transactional Email', 'postmark' => 'Postmark Transactional Email', ], - $this->config->get('mail.driver', 'smtp') + $this->config->get('mail.default', 'smtp') ); $method = 'setup' . studly_case($this->variables['MAIL_DRIVER']) . 'DriverVariables'; @@ -86,17 +86,17 @@ private function setupSmtpDriverVariables() { $this->variables['MAIL_HOST'] = $this->option('host') ?? $this->ask( trans('command/messages.environment.mail.ask_smtp_host'), - $this->config->get('mail.host') + $this->config->get('mail.mailers.smtp.host') ); $this->variables['MAIL_PORT'] = $this->option('port') ?? $this->ask( trans('command/messages.environment.mail.ask_smtp_port'), - $this->config->get('mail.port') + $this->config->get('mail.mailers.smtp.port') ); $this->variables['MAIL_USERNAME'] = $this->option('username') ?? $this->ask( trans('command/messages.environment.mail.ask_smtp_username'), - $this->config->get('mail.username') + $this->config->get('mail.mailers.smtp.username') ); $this->variables['MAIL_PASSWORD'] = $this->option('password') ?? $this->secret( diff --git a/app/Console/Commands/InfoCommand.php b/app/Console/Commands/InfoCommand.php index bc1d11792a..25c7744058 100644 --- a/app/Console/Commands/InfoCommand.php +++ b/app/Console/Commands/InfoCommand.php @@ -58,15 +58,16 @@ public function handle() ['Username', $this->config->get("database.connections.$driver.username")], ], 'compact'); + // TODO: Update this to handle other mail drivers $this->output->title('Email Configuration'); $this->table([], [ - ['Driver', $this->config->get('mail.driver')], - ['Host', $this->config->get('mail.host')], - ['Port', $this->config->get('mail.port')], - ['Username', $this->config->get('mail.username')], + ['Driver', $this->config->get('mail.default')], + ['Host', $this->config->get('mail.mailers.smtp.host')], + ['Port', $this->config->get('mail.mailers.smtp.port')], + ['Username', $this->config->get('mail.mailers.smtp.username')], ['From Address', $this->config->get('mail.from.address')], ['From Name', $this->config->get('mail.from.name')], - ['Encryption', $this->config->get('mail.encryption')], + ['Encryption', $this->config->get('mail.mailers.smtp.encryption')], ], 'compact'); } diff --git a/app/Http/Controllers/Admin/Settings/MailController.php b/app/Http/Controllers/Admin/Settings/MailController.php index 058e3a90e6..2db87fd58f 100644 --- a/app/Http/Controllers/Admin/Settings/MailController.php +++ b/app/Http/Controllers/Admin/Settings/MailController.php @@ -39,7 +39,7 @@ public function __construct( public function index(): View { return $this->view->make('admin.settings.mail', [ - 'disabled' => $this->config->get('mail.driver') !== 'smtp', + 'disabled' => $this->config->get('mail.default') !== 'smtp', ]); } @@ -52,7 +52,7 @@ public function index(): View */ public function update(MailSettingsFormRequest $request): Response { - if ($this->config->get('mail.driver') !== 'smtp') { + if ($this->config->get('mail.default') !== 'smtp') { throw new DisplayException('This feature is only available if SMTP is the selected email driver for the Panel.'); } diff --git a/app/Http/Controllers/Admin/UserController.php b/app/Http/Controllers/Admin/UserController.php index 254a72f8c9..1d6db65691 100644 --- a/app/Http/Controllers/Admin/UserController.php +++ b/app/Http/Controllers/Admin/UserController.php @@ -19,6 +19,7 @@ use Pterodactyl\Services\Users\UserCreationService; use Pterodactyl\Services\Users\UserDeletionService; use Pterodactyl\Http\Requests\Admin\UserFormRequest; +use Pterodactyl\Http\Requests\Admin\NewUserFormRequest; use Pterodactyl\Contracts\Repository\UserRepositoryInterface; class UserController extends Controller @@ -103,7 +104,7 @@ public function delete(Request $request, User $user): RedirectResponse * @throws \Exception * @throws \Throwable */ - public function store(UserFormRequest $request): RedirectResponse + public function store(NewUserFormRequest $request): RedirectResponse { $user = $this->creationService->handle($request->normalize()); $this->alert->success($this->translator->get('admin/user.notices.account_created'))->flash(); diff --git a/app/Http/Requests/Admin/NewUserFormRequest.php b/app/Http/Requests/Admin/NewUserFormRequest.php new file mode 100644 index 0000000000..d8c1993801 --- /dev/null +++ b/app/Http/Requests/Admin/NewUserFormRequest.php @@ -0,0 +1,28 @@ +only([ + 'email', + 'username', + 'name_first', + 'name_last', + 'password', + 'language', + 'root_admin', + ])->toArray(); + } +} diff --git a/app/Providers/SettingsServiceProvider.php b/app/Providers/SettingsServiceProvider.php index 5508ee0fbc..9c8a7445ec 100644 --- a/app/Providers/SettingsServiceProvider.php +++ b/app/Providers/SettingsServiceProvider.php @@ -61,7 +61,7 @@ public function boot(ConfigRepository $config, Encrypter $encrypter, Log $log, S { // Only set the email driver settings from the database if we // are configured using SMTP as the driver. - if ($config->get('mail.driver') === 'smtp') { + if ($config->get('mail.default') === 'smtp') { $this->keys = array_merge($this->keys, $this->emailKeys); } diff --git a/resources/views/admin/settings/mail.blade.php b/resources/views/admin/settings/mail.blade.php index 580271e02e..b134469954 100644 --- a/resources/views/admin/settings/mail.blade.php +++ b/resources/views/admin/settings/mail.blade.php @@ -38,14 +38,14 @@
- +

Enter the SMTP server address that mail should be sent through.

- +

Enter the SMTP server port that mail should be sent through.

@@ -53,7 +53,7 @@
@php - $encryption = old('mail:encryption', config('mail.encryption')); + $encryption = old('mail:encryption', config('mail.mailers.smtp.encryption')); @endphp +

The username to use when connecting to the SMTP server.

From d31ece1c757e9f47305447da134bad64207f6e1e Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Mon, 24 Oct 2022 09:48:30 -0600 Subject: [PATCH 255/458] nix: add flake with dev shell --- .editorconfig | 12 +++++++----- .gitignore | 1 + flake.lock | 43 +++++++++++++++++++++++++++++++++++++++++++ flake.nix | 22 ++++++++++++++++++++++ shell.nix | 17 +++++++++++++++++ 5 files changed, 90 insertions(+), 5 deletions(-) create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 shell.nix diff --git a/.editorconfig b/.editorconfig index c70b7be22c..a041471162 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,15 +1,17 @@ root = true [*] -end_of_line = lf -insert_final_newline = true indent_style = space indent_size = 4 +tab_width = 4 +end_of_line = lf charset = utf-8 trim_trailing_whitespace = true - -[*.yml] -indent_size = 2 +insert_final_newline = true [*.md] trim_trailing_whitespace = false + +[*.{md,nix,yml,yaml}] +indent_size = 2 +tab_width = 2 diff --git a/.gitignore b/.gitignore index cf911a1de9..4b5d022467 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ storage/framework/* /.idea /nbproject +/.direnv node_modules *.log diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000000..4a64ac9647 --- /dev/null +++ b/flake.lock @@ -0,0 +1,43 @@ +{ + "nodes": { + "flake-utils": { + "locked": { + "lastModified": 1659877975, + "narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1666539104, + "narHash": "sha256-jeuC+d375wHHxMOFLgu7etseCQVJuPNKoEc9X9CsErg=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "0e6df35f39651504249a05191f9a78d251707e22", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000000..050cc2415c --- /dev/null +++ b/flake.nix @@ -0,0 +1,22 @@ +{ + description = "Pterodactyl Panel"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + flake-utils.url = "github:numtide/flake-utils"; + }; + + outputs = { + self, + nixpkgs, + flake-utils, + ... + }: + flake-utils.lib.eachDefaultSystem ( + system: let + pkgs = import nixpkgs {inherit system;}; + in { + devShell = import ./shell.nix {inherit pkgs;}; + } + ); +} diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000000..afe21af5e1 --- /dev/null +++ b/shell.nix @@ -0,0 +1,17 @@ +{pkgs ? import {}}: +with pkgs; + mkShell rec { + buildInputs = [ + alejandra + (php81.buildEnv { + extensions = ({ enabled, all }: enabled ++ (with all; [ + redis + xdebug + ])); + extraConfig = '' + xdebug.mode=debug + ''; + }) + php81Packages.composer + ]; + } From 5c78b380c5c4cea06e5bc9a7e347231387f97706 Mon Sep 17 00:00:00 2001 From: Jelco <53396500+Jelcoo@users.noreply.github.com> Date: Wed, 26 Oct 2022 18:26:56 +0200 Subject: [PATCH 256/458] Fix InMemoryFilesystemAdapter (#4489) --- app/Extensions/Backups/BackupManager.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Extensions/Backups/BackupManager.php b/app/Extensions/Backups/BackupManager.php index 32df8f22d5..944e8d7908 100644 --- a/app/Extensions/Backups/BackupManager.php +++ b/app/Extensions/Backups/BackupManager.php @@ -104,7 +104,7 @@ protected function callCustomCreator(array $config): mixed */ public function createWingsAdapter(array $config): FilesystemAdapter { - return new InMemoryFilesystemAdapter(null); + return new InMemoryFilesystemAdapter(); } /** From 6f0bb43314ad86e4a721637f924aa8de27f97f37 Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Wed, 26 Oct 2022 10:43:05 -0600 Subject: [PATCH 257/458] ci: cleanup, add dedicated lint workflow --- .github/workflows/{build.yml => build.yaml} | 11 ++++--- .github/workflows/{tests.yml => ci.yaml} | 24 +++++++-------- .github/workflows/{docker.yml => docker.yaml} | 4 +-- .github/workflows/lint.yaml | 29 +++++++++++++++++++ .../workflows/{release.yml => release.yaml} | 4 +-- 5 files changed, 51 insertions(+), 21 deletions(-) rename .github/workflows/{build.yml => build.yaml} (73%) rename .github/workflows/{tests.yml => ci.yaml} (72%) rename .github/workflows/{docker.yml => docker.yaml} (97%) create mode 100644 .github/workflows/lint.yaml rename .github/workflows/{release.yml => release.yaml} (98%) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yaml similarity index 73% rename from .github/workflows/build.yml rename to .github/workflows/build.yaml index 5b4b7c5caf..4aca84ce80 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yaml @@ -3,16 +3,19 @@ name: Build on: push: branches: - - 'develop' - - 'v2' + - "develop" + - "1.0-develop" pull_request: + branches: + - "develop" + - "1.0-develop" jobs: ui: name: UI runs-on: ubuntu-20.04 - if: "!contains(github.event.head_commit.message, 'skip ci') && !contains(github.event.head_commit.message, 'ci skip')" strategy: + fail-fast: false matrix: node-version: [16] steps: @@ -23,7 +26,7 @@ jobs: uses: actions/setup-node@v3 with: node-version: ${{ matrix.node-version }} - cache: 'yarn' + cache: "yarn" - name: Install dependencies run: yarn install --frozen-lockfile diff --git a/.github/workflows/tests.yml b/.github/workflows/ci.yaml similarity index 72% rename from .github/workflows/tests.yml rename to .github/workflows/ci.yaml index 5486076dd7..0e6f93b66f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/ci.yaml @@ -3,20 +3,22 @@ name: Tests on: push: branches: - - 'develop' - - 'v2' + - "develop" + - "1.0-develop" pull_request: + branches: + - "develop" + - "1.0-develop" jobs: tests: name: Tests runs-on: ubuntu-20.04 - if: "!contains(github.event.head_commit.message, 'skip ci') && !contains(github.event.head_commit.message, 'ci skip')" strategy: fail-fast: false matrix: php: [8.0, 8.1] - database: ['mariadb:10.2', 'mysql:8'] + database: ["mariadb:10.2", "mysql:8"] services: database: image: ${{ matrix.database }} @@ -33,15 +35,15 @@ jobs: - name: Get cache directory id: composer-cache run: | - echo "::set-output name=dir::$(composer config cache-files-dir)" + echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT - name: Cache uses: actions/cache@v3 with: - path: | - ~/.php_cs.cache - ${{ steps.composer-cache.outputs.dir }} - key: ${{ runner.os }}-cache-${{ matrix.php }}-${{ hashFiles('**.composer.lock') }} + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ matrix.php }}-${{ hashFiles('**/composer.lock') }} + restore-keys: | + ${{ runner.os }}-composer-${{ matrix.php }}- - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -57,10 +59,6 @@ jobs: - name: Install dependencies run: composer install --no-interaction --no-progress --no-suggest --prefer-dist - - name: PHP CS Fixer - run: vendor/bin/php-cs-fixer fix --dry-run --diff - continue-on-error: true - - name: Unit tests run: vendor/bin/phpunit --bootstrap vendor/autoload.php tests/Unit if: ${{ always() }} diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yaml similarity index 97% rename from .github/workflows/docker.yml rename to .github/workflows/docker.yaml index 1f7a44d5a6..0e1a2b77e8 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yaml @@ -3,8 +3,8 @@ name: Publish Docker Image on: push: branches: - - 'develop' - - 'release/v*' + - "develop" + - "release/v*" jobs: push: diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml new file mode 100644 index 0000000000..709af43646 --- /dev/null +++ b/.github/workflows/lint.yaml @@ -0,0 +1,29 @@ +name: Lint + +on: + push: + branches: + - "develop" + - "1.0-develop" + pull_request: + branches: + - "develop" + - "1.0-develop" + +jobs: + lint: + name: Lint + runs-on: ubuntu-20.04 + steps: + - name: Code Checkout + uses: actions/checkout@v3 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.1' + tools: composer:v2 + coverage: none + + - name: PHP CS Fixer + run: vendor/bin/php-cs-fixer fix --dry-run --diff diff --git a/.github/workflows/release.yml b/.github/workflows/release.yaml similarity index 98% rename from .github/workflows/release.yml rename to .github/workflows/release.yaml index 26d5b90345..09ab86410a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yaml @@ -3,7 +3,7 @@ name: Release on: push: tags: - - 'v*' + - "v*" jobs: release: @@ -17,7 +17,7 @@ jobs: uses: actions/setup-node@v3 with: node-version: 16 - cache: 'yarn' + cache: "yarn" - name: Install dependencies run: yarn install --frozen-lockfile From 5f57e63fe484cb649c64b745fb1250dc6d53b1e4 Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Wed, 26 Oct 2022 10:45:43 -0600 Subject: [PATCH 258/458] ci(lint): install composer dependencies --- .github/workflows/lint.yaml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 709af43646..4ac292b686 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -21,9 +21,16 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: '8.1' + php-version: "8.1" + extensions: bcmath, curl, gd, mbstring, mysql, openssl, pdo, tokenizer, xml, zip tools: composer:v2 coverage: none + - name: Setup .env + run: cp .env.ci .env + + - name: Install dependencies + run: composer install --no-interaction --no-progress --no-suggest --prefer-dist + - name: PHP CS Fixer run: vendor/bin/php-cs-fixer fix --dry-run --diff From 548affba84f3478186fb926dd122d9060dc3bcbd Mon Sep 17 00:00:00 2001 From: Lance Pioch Date: Sat, 29 Oct 2022 19:58:55 -0400 Subject: [PATCH 259/458] Fix linting (#4504) --- app/Http/Controllers/Admin/Servers/ServerTransferController.php | 2 +- app/Models/Model.php | 2 +- app/Models/UserSSHKey.php | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/Admin/Servers/ServerTransferController.php b/app/Http/Controllers/Admin/Servers/ServerTransferController.php index cdb2bb5579..0962174186 100644 --- a/app/Http/Controllers/Admin/Servers/ServerTransferController.php +++ b/app/Http/Controllers/Admin/Servers/ServerTransferController.php @@ -4,8 +4,8 @@ use Illuminate\Http\Request; use Pterodactyl\Models\Server; -use Prologue\Alerts\AlertsMessageBag; use Illuminate\Http\RedirectResponse; +use Prologue\Alerts\AlertsMessageBag; use Pterodactyl\Models\ServerTransfer; use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Services\Servers\TransferService; diff --git a/app/Models/Model.php b/app/Models/Model.php index c5bc666d15..2e371d9d9c 100644 --- a/app/Models/Model.php +++ b/app/Models/Model.php @@ -2,9 +2,9 @@ namespace Pterodactyl\Models; +use Carbon\CarbonImmutable; use Illuminate\Support\Arr; use Illuminate\Support\Str; -use Carbon\CarbonImmutable; use Illuminate\Support\Carbon; use Illuminate\Validation\Rule; use Illuminate\Container\Container; diff --git a/app/Models/UserSSHKey.php b/app/Models/UserSSHKey.php index 0aac52310e..fb3c8f395d 100644 --- a/app/Models/UserSSHKey.php +++ b/app/Models/UserSSHKey.php @@ -32,6 +32,7 @@ * @method static \Illuminate\Database\Eloquent\Builder|UserSSHKey whereUserId($value) * @method static \Illuminate\Database\Query\Builder|UserSSHKey withTrashed() * @method static \Illuminate\Database\Query\Builder|UserSSHKey withoutTrashed() + * * @mixin \Eloquent * * @method static \Database\Factories\UserSSHKeyFactory factory(...$parameters) From b1abae8106e40858b3b97f9b4a8a325c59d01131 Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Sun, 30 Oct 2022 13:58:29 -0600 Subject: [PATCH 260/458] Update README.md --- README.md | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 9ac8a0d15e..79efc44e6d 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ ![GitHub contributors](https://img.shields.io/github/contributors/pterodactyl/panel?style=for-the-badge) # Pterodactyl Panel + Pterodactyl® is a free, open-source game server management panel built with PHP, React, and Go. Designed with security in mind, Pterodactyl runs all game servers in isolated Docker containers while exposing a beautiful and intuitive UI to end users. @@ -15,30 +16,31 @@ Stop settling for less. Make game servers a first class citizen on your platform ![Image](https://cdn.pterodactyl.io/site-assets/pterodactyl_v1_demo.gif) ## Documentation + * [Panel Documentation](https://pterodactyl.io/panel/1.0/getting_started.html) * [Wings Documentation](https://pterodactyl.io/wings/1.0/installing.html) * [Community Guides](https://pterodactyl.io/community/about.html) * Or, get additional help [via Discord](https://discord.gg/pterodactyl) ## Sponsors + I would like to extend my sincere thanks to the following sponsors for helping fund Pterodactyl's developement. [Interested in becoming a sponsor?](https://github.com/sponsors/matthewpi) -| Company | About | -| ------- | ----- | -| [**WISP**](https://wisp.gg) | Extra features. | -| [**BisectHosting**](https://www.bisecthosting.com/) | BisectHosting provides Minecraft, Valheim and other server hosting services with the highest reliability and lightning fast support since 2012. | -| [**Fragnet**](https://fragnet.net) | Providing low latency, high-end game hosting solutions to gamers, game studios and eSports platforms. | -| [**Tempest**](https://tempest.net/) | Tempest Hosting is a subsidiary of Path Network, Inc. offering unmetered DDoS protected 10Gbps dedicated servers, starting at just $80/month. Full anycast, tons of filters. | -| [**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 the most highend French hosting company for your minecraft server? More than 24,000 members on our discord trust us. Give us a try! | -| [**Skynode**](https://www.skynode.pro/) | Skynode provides blazing fast game servers along with a top-notch user experience. Whatever our clients are looking for, we're able to provide it! | -| [**DeinServerHost**](https://deinserverhost.de/) | DeinServerHost offers Dedicated, vps and Gameservers for many popular Games like Minecraft and Rust in Germany since 2013. | -| [**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. | -| [**VibeGAMES**](https://vibegames.net/) | VibeGAMES is a game server provider that specializes in DDOS protection for the games we offer. We have multiple locations in the US, Brazil, France, Germany, Singapore, Australia and South Africa.| -| [**Gamenodes**](https://gamenodes.nl) | Gamenodes love quality. For Minecraft, Discord Bots and other services, among others. With our own programmers, we provide just that little bit of extra service! | +| Company | About | +|-----------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| [**WISP**](https://wisp.gg) | Extra features. | +| [**Fragnet**](https://fragnet.net) | Providing low latency, high-end game hosting solutions to gamers, game studios and eSports platforms. | +| [**RocketNode**](https://rocketnode.com/) | Innovative game server hosting combined with a straightforward control panel, affordable prices, and Rocket-Fast support. | +| [**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. | +| [**BisectHosting**](https://www.bisecthosting.com/) | BisectHosting provides Minecraft, Valheim and other server hosting services with the highest reliability and lightning fast support since 2012. | +| [**MineStrator**](https://minestrator.com/) | Looking for the most highend French hosting company for your minecraft server? More than 24,000 members on our discord trust us. Give us a try! | +| [**Skynode**](https://www.skynode.pro/) | Skynode provides blazing fast game servers along with a top-notch user experience. Whatever our clients are looking for, we're able to provide it! | +| [**VibeGAMES**](https://vibegames.net/) | VibeGAMES is a game server provider that specializes in DDOS protection for the games we offer. We have multiple locations in the US, Brazil, France, Germany, Singapore, Australia and South Africa. | +| [**Pterodactyl Market**](https://pterodactylmarket.com/) | Pterodactyl Market is a one-and-stop shop for Pterodactyl. In our market, you can find Add-ons, Themes, Eggs, and more for Pterodactyl. | ### Supported Games + Pterodactyl supports a wide variety of games by utilizing Docker containers to isolate each instance. This gives you the power to run game servers without bloating machines with a host of additional dependencies. @@ -67,6 +69,7 @@ and there are plenty more games available provided by the community. Some of the * [and many more...](https://github.com/parkervcp/eggs) ## License + Pterodactyl® Copyright © 2015 - 2022 Dane Everitt and contributors. Code released under the [MIT License](./LICENSE.md). From f2095e815ed93cb958e857d6dc3f93510845b5f2 Mon Sep 17 00:00:00 2001 From: Boy132 Date: Mon, 31 Oct 2022 17:20:53 +0100 Subject: [PATCH 261/458] Allow users to change the server description (#4420) --- .../Api/Client/Servers/SettingsController.php | 1 + .../Servers/Settings/RenameServerRequest.php | 3 ++- app/Models/Permission.php | 2 +- resources/lang/en/activity.php | 1 + resources/scripts/api/server/renameServer.ts | 4 ++-- .../server/settings/RenameServerBox.tsx | 22 ++++++++++++++----- .../Client/Server/SettingsControllerTest.php | 5 +++++ 7 files changed, 29 insertions(+), 9 deletions(-) diff --git a/app/Http/Controllers/Api/Client/Servers/SettingsController.php b/app/Http/Controllers/Api/Client/Servers/SettingsController.php index 7f1946a212..d1377d67a3 100644 --- a/app/Http/Controllers/Api/Client/Servers/SettingsController.php +++ b/app/Http/Controllers/Api/Client/Servers/SettingsController.php @@ -36,6 +36,7 @@ public function rename(RenameServerRequest $request, Server $server): JsonRespon { $this->repository->update($server->id, [ 'name' => $request->input('name'), + 'description' => $request->input('description') ?? '', ]); if ($server->name !== $request->input('name')) { diff --git a/app/Http/Requests/Api/Client/Servers/Settings/RenameServerRequest.php b/app/Http/Requests/Api/Client/Servers/Settings/RenameServerRequest.php index 8cb5efa35c..4a74f0a0e6 100644 --- a/app/Http/Requests/Api/Client/Servers/Settings/RenameServerRequest.php +++ b/app/Http/Requests/Api/Client/Servers/Settings/RenameServerRequest.php @@ -11,7 +11,7 @@ class RenameServerRequest extends ClientApiRequest implements ClientPermissionsR { /** * Returns the permissions string indicating which permission should be used to - * validate that the authenticated user has permission to perform this action aganist + * validate that the authenticated user has permission to perform this action against * the given resource (server). */ public function permission(): string @@ -26,6 +26,7 @@ public function rules(): array { return [ 'name' => Server::getRules()['name'], + 'description' => 'string|nullable', ]; } } diff --git a/app/Models/Permission.php b/app/Models/Permission.php index 6d27a90d30..b2e00070e7 100644 --- a/app/Models/Permission.php +++ b/app/Models/Permission.php @@ -195,7 +195,7 @@ class Permission extends Model 'settings' => [ 'description' => 'Permissions that control a user\'s access to the settings for this server.', 'keys' => [ - 'rename' => 'Allows a user to rename this server.', + 'rename' => 'Allows a user to rename this server and change the description of it.', 'reinstall' => 'Allows a user to trigger a reinstall of this server.', ], ], diff --git a/resources/lang/en/activity.php b/resources/lang/en/activity.php index afa6558065..501a1dcde6 100644 --- a/resources/lang/en/activity.php +++ b/resources/lang/en/activity.php @@ -115,6 +115,7 @@ ], 'settings' => [ 'rename' => 'Renamed the server from :old to :new', + 'description' => 'Changed the server description from :old to :new', ], 'startup' => [ 'edit' => 'Changed the :variable variable from ":old" to ":new"', diff --git a/resources/scripts/api/server/renameServer.ts b/resources/scripts/api/server/renameServer.ts index a4a95141e2..4622f9d4d7 100644 --- a/resources/scripts/api/server/renameServer.ts +++ b/resources/scripts/api/server/renameServer.ts @@ -1,8 +1,8 @@ import http from '@/api/http'; -export default (uuid: string, name: string): Promise => { +export default (uuid: string, name: string, description?: string): Promise => { return new Promise((resolve, reject) => { - http.post(`/api/client/servers/${uuid}/settings/rename`, { name }) + http.post(`/api/client/servers/${uuid}/settings/rename`, { name, description }) .then(() => resolve()) .catch(reject); }); diff --git a/resources/scripts/components/server/settings/RenameServerBox.tsx b/resources/scripts/components/server/settings/RenameServerBox.tsx index 98be2ef9f9..9cb290a1ad 100644 --- a/resources/scripts/components/server/settings/RenameServerBox.tsx +++ b/resources/scripts/components/server/settings/RenameServerBox.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { ServerContext } from '@/state/server'; import TitledGreyBox from '@/components/elements/TitledGreyBox'; -import { Form, Formik, FormikHelpers, useFormikContext } from 'formik'; +import { Field as FormikField, Form, Formik, FormikHelpers, useFormikContext } from 'formik'; import { Actions, useStoreActions } from 'easy-peasy'; import renameServer from '@/api/server/renameServer'; import Field from '@/components/elements/Field'; @@ -11,19 +11,29 @@ import { ApplicationStore } from '@/state'; import { httpErrorToHuman } from '@/api/http'; import { Button } from '@/components/elements/button/index'; import tw from 'twin.macro'; +import Label from '@/components/elements/Label'; +import FormikFieldWrapper from '@/components/elements/FormikFieldWrapper'; +import { Textarea } from '@/components/elements/Input'; interface Values { name: string; + description: string; } const RenameServerBox = () => { const { isSubmitting } = useFormikContext(); return ( - +
+
+ + + + +
@@ -37,10 +47,10 @@ export default () => { const setServer = ServerContext.useStoreActions((actions) => actions.server.setServer); const { addError, clearFlashes } = useStoreActions((actions: Actions) => actions.flashes); - const submit = ({ name }: Values, { setSubmitting }: FormikHelpers) => { + const submit = ({ name, description }: Values, { setSubmitting }: FormikHelpers) => { clearFlashes('settings'); - renameServer(server.uuid, name) - .then(() => setServer({ ...server, name })) + renameServer(server.uuid, name, description) + .then(() => setServer({ ...server, name, description })) .catch((error) => { console.error(error); addError({ key: 'settings', message: httpErrorToHuman(error) }); @@ -53,9 +63,11 @@ export default () => { onSubmit={submit} initialValues={{ name: server.name, + description: server.description, }} validationSchema={object().shape({ name: string().required().min(1), + description: string().nullable(), })} > diff --git a/tests/Integration/Api/Client/Server/SettingsControllerTest.php b/tests/Integration/Api/Client/Server/SettingsControllerTest.php index c92754cfa1..882314bea1 100644 --- a/tests/Integration/Api/Client/Server/SettingsControllerTest.php +++ b/tests/Integration/Api/Client/Server/SettingsControllerTest.php @@ -21,9 +21,11 @@ public function testServerNameCanBeChanged(array $permissions) /** @var \Pterodactyl\Models\Server $server */ [$user, $server] = $this->generateTestAccount($permissions); $originalName = $server->name; + $originalDescription = $server->description; $response = $this->actingAs($user)->postJson("/api/client/servers/$server->uuid/settings/rename", [ 'name' => '', + 'description' => '', ]); $response->assertStatus(Response::HTTP_UNPROCESSABLE_ENTITY); @@ -31,15 +33,18 @@ public function testServerNameCanBeChanged(array $permissions) $server = $server->refresh(); $this->assertSame($originalName, $server->name); + $this->assertSame($originalDescription, $server->description); $this->actingAs($user) ->postJson("/api/client/servers/$server->uuid/settings/rename", [ 'name' => 'Test Server Name', + 'description' => 'This is a test server.', ]) ->assertStatus(Response::HTTP_NO_CONTENT); $server = $server->refresh(); $this->assertSame('Test Server Name', $server->name); + $this->assertSame('This is a test server.', $server->description); } /** From 16c2b606b44e0420dedb50a0098f4c8bb86e6f46 Mon Sep 17 00:00:00 2001 From: Lance Pioch Date: Mon, 31 Oct 2022 12:29:10 -0400 Subject: [PATCH 262/458] Add ManifestDoesNotExistException and Solution (#4455) Co-authored-by: Matthew Penner --- .../ManifestDoesNotExistException.php | 15 +++++++++++ .../ManifestDoesNotExistSolution.php | 25 +++++++++++++++++++ app/Services/Helpers/AssetHashService.php | 8 +++++- 3 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 app/Exceptions/ManifestDoesNotExistException.php create mode 100644 app/Exceptions/Solutions/ManifestDoesNotExistSolution.php diff --git a/app/Exceptions/ManifestDoesNotExistException.php b/app/Exceptions/ManifestDoesNotExistException.php new file mode 100644 index 0000000000..2769cd03c3 --- /dev/null +++ b/app/Exceptions/ManifestDoesNotExistException.php @@ -0,0 +1,15 @@ + 'https://github.com/pterodactyl/panel/blob/develop/package.json', + ]; + } +} diff --git a/app/Services/Helpers/AssetHashService.php b/app/Services/Helpers/AssetHashService.php index 2e1c1aaa65..725a566692 100644 --- a/app/Services/Helpers/AssetHashService.php +++ b/app/Services/Helpers/AssetHashService.php @@ -5,6 +5,7 @@ use Illuminate\Support\Arr; use Illuminate\Filesystem\FilesystemManager; use Illuminate\Contracts\Filesystem\Filesystem; +use Pterodactyl\Exceptions\ManifestDoesNotExistException; class AssetHashService { @@ -106,6 +107,11 @@ protected function manifest(): array ); } - return static::$manifest; + $manifest = static::$manifest; + if ($manifest === null) { + throw new ManifestDoesNotExistException(); + } + + return $manifest; } } From e21aab2537afa349a2ee8d41b7145d44bc8cd1a4 Mon Sep 17 00:00:00 2001 From: Quinten <67589015+QuintenQVD0@users.noreply.github.com> Date: Mon, 31 Oct 2022 17:29:29 +0100 Subject: [PATCH 263/458] Update mumble egg (#4437) --- .../eggs/voice-servers/egg-mumble-server.json | 32 +++++++------------ 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/database/Seeders/eggs/voice-servers/egg-mumble-server.json b/database/Seeders/eggs/voice-servers/egg-mumble-server.json index 27f60e7173..feac4dc1a8 100644 --- a/database/Seeders/eggs/voice-servers/egg-mumble-server.json +++ b/database/Seeders/eggs/voice-servers/egg-mumble-server.json @@ -1,28 +1,28 @@ { "_comment": "DO NOT EDIT: FILE GENERATED AUTOMATICALLY BY PTERODACTYL PANEL - PTERODACTYL.IO", "meta": { - "version": "PTDL_v1", + "version": "PTDL_v2", "update_url": null }, - "exported_at": "2021-06-15T16:54:54-04:00", + "exported_at": "2022-10-15T12:38:18+02:00", "name": "Mumble Server", "author": "support@pterodactyl.io", "description": "Mumble is an open source, low-latency, high quality voice chat software primarily intended for use while gaming.", "features": null, - "images": [ - "ghcr.io\/pterodactyl\/yolks:alpine" - ], + "docker_images": { + "Mumble": "ghcr.io\/parkervcp\/yolks:voice_mumble" + }, "file_denylist": [], - "startup": ".\/murmur.x86 -fg", + "startup": "mumble-server -fg -ini murmur.ini", "config": { - "files": "{\r\n \"murmur.ini\": {\r\n \"parser\": \"ini\",\r\n \"find\": {\r\n \"logfile\": \"murmur.log\",\r\n \"port\": \"{{server.build.default.port}}\",\r\n \"host\": \"0.0.0.0\",\r\n \"users\": \"{{server.build.env.MAX_USERS}}\"\r\n }\r\n }\r\n}", + "files": "{\r\n \"murmur.ini\": {\r\n \"parser\": \"ini\",\r\n \"find\": {\r\n \"database\": \"\/home\/container\/murmur.sqlite\",\r\n \"logfile\": \"\/home\/container\/murmur.log\",\r\n \"port\": \"{{server.build.default.port}}\",\r\n \"host\": \"0.0.0.0\",\r\n \"users\": \"{{server.build.env.MAX_USERS}}\"\r\n }\r\n }\r\n}", "startup": "{\r\n \"done\": \"Server listening on\"\r\n}", - "logs": "{\r\n \"custom\": true,\r\n \"location\": \"logs\/murmur.log\"\r\n}", + "logs": "{}", "stop": "^C" }, "scripts": { "installation": { - "script": "#!\/bin\/ash\r\n# Mumble Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\nGITHUB_PACKAGE=mumble-voip\/mumble\r\nMATCH=murmur-static\r\n\r\nif [ ! -d \/mnt\/server\/ ]; then\r\n mkdir \/mnt\/server\/\r\nfi\r\n\r\ncd \/mnt\/server\r\n\r\nif [ -z \"${GITHUB_USER}\" ] && [ -z \"${GITHUB_OAUTH_TOKEN}\" ] ; then\r\n echo -e \"using anon api call\"\r\nelse\r\n echo -e \"user and oauth token set\"\r\n alias curl='curl -u ${GITHUB_USER}:${GITHUB_OAUTH_TOKEN} '\r\nfi\r\n\r\n## get release info and download links\r\nLATEST_JSON=$(curl --silent \"https:\/\/api.github.com\/repos\/${GITHUB_PACKAGE}\/releases\/latest\")\r\nRELEASES=$(curl --silent \"https:\/\/api.github.com\/repos\/${GITHUB_PACKAGE}\/releases\")\r\n\r\nif [ -z \"${VERSION}\" ] || [ \"${VERSION}\" == \"latest\" ]; then\r\n DOWNLOAD_LINK=$(echo ${LATEST_JSON} | jq .assets | jq -r .[].browser_download_url | grep -m 1 -i ${MATCH})\r\nelse\r\n VERSION_CHECK=$(echo ${RELEASES} | jq -r --arg VERSION \"${VERSION}\" '.[] | select(.tag_name==$VERSION) | .tag_name')\r\n if [ \"${VERSION}\" == \"${VERSION_CHECK}\" ]; then\r\n DOWNLOAD_LINK=$(echo ${RELEASES} | jq -r --arg VERSION \"${VERSION}\" '.[] | select(.tag_name==$VERSION) | .assets[].browser_download_url' | grep -m 1 -i ${MATCH})\r\n else\r\n echo -e \"defaulting to latest release\"\r\n DOWNLOAD_LINK=$(echo ${LATEST_JSON} | jq .assets | jq -r .[].browser_download_url)\r\n fi\r\nfi\r\n\r\ncurl -L ${DOWNLOAD_LINK} | tar xjv --strip-components=1", + "script": "#!\/bin\/ash\r\n\r\nif [ ! -d \/mnt\/server\/ ]; then\r\n mkdir \/mnt\/server\/\r\nfi\r\n\r\ncd \/mnt\/server\r\n\r\nFILE=\/mnt\/server\/murmur.ini\r\nif [ -f \"$FILE\" ]; then\r\n echo \"Config file already exists.\"\r\nelse \r\n echo \"Downloading the config file.\"\r\n apk add --no-cache murmur\r\n cp \/etc\/murmur.ini \/mnt\/server\/murmur.ini\r\n apk del murmur\r\nfi\r\necho \"done\"", "container": "ghcr.io\/pterodactyl\/installers:alpine", "entrypoint": "ash" } @@ -35,16 +35,8 @@ "default_value": "100", "user_viewable": true, "user_editable": false, - "rules": "required|numeric|digits_between:1,5" - }, - { - "name": "Server Version", - "description": "Version of Mumble Server to download and use.", - "env_variable": "MUMBLE_VERSION", - "default_value": "latest", - "user_viewable": true, - "user_editable": true, - "rules": "required|string" + "rules": "required|numeric|digits_between:1,5", + "field_type": "text" } ] -} \ No newline at end of file +} From 1b780302910b1451c119b303239da33106c6bc00 Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Mon, 31 Oct 2022 10:58:32 -0600 Subject: [PATCH 264/458] Fix compatibility with old queue names --- config/mail.php | 4 ++-- config/queue.php | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/config/mail.php b/config/mail.php index 0f9639291d..5e910befec 100644 --- a/config/mail.php +++ b/config/mail.php @@ -91,8 +91,8 @@ */ 'from' => [ - 'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'), - 'name' => env('MAIL_FROM_NAME', 'Example'), + 'address' => env('MAIL_FROM_ADDRESS', env('MAIL_FROM')), + 'name' => env('MAIL_FROM_NAME', 'Pterodactyl Panel'), ], /* diff --git a/config/queue.php b/config/queue.php index 35585df0e8..39b93eefb2 100644 --- a/config/queue.php +++ b/config/queue.php @@ -33,7 +33,7 @@ 'database' => [ 'driver' => 'database', 'table' => 'jobs', - 'queue' => 'default', + 'queue' => env('QUEUE_STANDARD', 'default'), 'retry_after' => 90, 'after_commit' => false, ], @@ -43,7 +43,7 @@ 'key' => env('AWS_ACCESS_KEY_ID'), 'secret' => env('AWS_SECRET_ACCESS_KEY'), 'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'), - 'queue' => env('SQS_QUEUE', 'default'), + 'queue' => env('SQS_QUEUE', env('QUEUE_STANDARD', 'default')), 'suffix' => env('SQS_SUFFIX'), 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), 'after_commit' => false, @@ -52,7 +52,7 @@ 'redis' => [ 'driver' => 'redis', 'connection' => 'default', - 'queue' => env('REDIS_QUEUE', 'default'), + 'queue' => env('REDIS_QUEUE', env('QUEUE_STANDARD', 'default')), 'retry_after' => 90, 'block_for' => null, 'after_commit' => false, From 846ff7644f60018af6a79b64174d8799ea2c169c Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Mon, 31 Oct 2022 11:02:34 -0600 Subject: [PATCH 265/458] Fix missing email sender --- config/mail.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/mail.php b/config/mail.php index 5e910befec..95c6f303cf 100644 --- a/config/mail.php +++ b/config/mail.php @@ -91,7 +91,7 @@ */ 'from' => [ - 'address' => env('MAIL_FROM_ADDRESS', env('MAIL_FROM')), + 'address' => env('MAIL_FROM_ADDRESS', env('MAIL_FROM', 'hello@example.com')), 'name' => env('MAIL_FROM_NAME', 'Pterodactyl Panel'), ], From d466934103f2dc062498dc7ffaeb82f1d1b46867 Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Mon, 31 Oct 2022 11:38:56 -0600 Subject: [PATCH 266/458] Laravel Sail (#4508) --- .env.ci | 2 +- .github/workflows/ci.yaml | 2 +- bootstrap/cache/.gitignore | 0 bootstrap/tests.php | 3 +- composer.json | 3 +- composer.lock | 158 +++++++++++++++++++++++++------------ phpunit.xml | 2 +- 7 files changed, 116 insertions(+), 54 deletions(-) mode change 100755 => 100644 bootstrap/cache/.gitignore diff --git a/.env.ci b/.env.ci index e893452d44..1a9e848e3b 100644 --- a/.env.ci +++ b/.env.ci @@ -8,7 +8,7 @@ APP_ENVIRONMENT_ONLY=true DB_CONNECTION=mysql DB_HOST=127.0.0.1 -DB_DATABASE=panel_test +DB_DATABASE=testing DB_USERNAME=root DB_PASSWORD= diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 0e6f93b66f..4da19b6ed2 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -24,7 +24,7 @@ jobs: image: ${{ matrix.database }} env: MYSQL_ALLOW_EMPTY_PASSWORD: yes - MYSQL_DATABASE: panel_test + MYSQL_DATABASE: testing ports: - 3306 options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 diff --git a/bootstrap/cache/.gitignore b/bootstrap/cache/.gitignore old mode 100755 new mode 100644 diff --git a/bootstrap/tests.php b/bootstrap/tests.php index 35479a0cd6..5b54493554 100644 --- a/bootstrap/tests.php +++ b/bootstrap/tests.php @@ -1,5 +1,6 @@ writeln(PHP_EOL . 'Cannot run test process against non-testing database.'); $output->writeln(PHP_EOL . 'Environment is currently pointed at: "' . config("$prefix.database") . '".'); exit(1); diff --git a/composer.json b/composer.json index 2d40267819..160537b6bb 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,7 @@ } ], "require": { - "php": "^8.0.2 || ^8.1", + "php": "^8.0.2 || ^8.1 || ^8.2", "ext-json": "*", "ext-mbstring": "*", "ext-pdo": "*", @@ -58,6 +58,7 @@ "fakerphp/faker": "~1.20", "friendsofphp/php-cs-fixer": "~3.11", "itsgoingd/clockwork": "~5.1", + "laravel/sail": "~1.16", "mockery/mockery": "~1.5", "nunomaduro/collision": "~6.3", "php-mock/php-mock-phpunit": "~2.6", diff --git a/composer.lock b/composer.lock index a506d4edc2..9f4b73f6f9 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "80a0f1016b1ba9e0b31d6cfe0f27f8c1", + "content-hash": "ae61e7d6e405e3a59c8a54f3eefa2c50", "packages": [ { "name": "aws/aws-crt-php", @@ -376,16 +376,16 @@ }, { "name": "doctrine/dbal", - "version": "3.4.5", + "version": "3.4.6", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "a5a58773109c0abb13e658c8ccd92aeec8d07f9e" + "reference": "3ce132f7c0b83d33b26ab6ed308e9e9260699bc4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/a5a58773109c0abb13e658c8ccd92aeec8d07f9e", - "reference": "a5a58773109c0abb13e658c8ccd92aeec8d07f9e", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/3ce132f7c0b83d33b26ab6ed308e9e9260699bc4", + "reference": "3ce132f7c0b83d33b26ab6ed308e9e9260699bc4", "shasum": "" }, "require": { @@ -400,14 +400,14 @@ "require-dev": { "doctrine/coding-standard": "10.0.0", "jetbrains/phpstorm-stubs": "2022.2", - "phpstan/phpstan": "1.8.3", - "phpstan/phpstan-strict-rules": "^1.3", - "phpunit/phpunit": "9.5.24", + "phpstan/phpstan": "1.8.10", + "phpstan/phpstan-strict-rules": "^1.4", + "phpunit/phpunit": "9.5.25", "psalm/plugin-phpunit": "0.17.0", "squizlabs/php_codesniffer": "3.7.1", "symfony/cache": "^5.4|^6.0", "symfony/console": "^4.4|^5.4|^6.0", - "vimeo/psalm": "4.27.0" + "vimeo/psalm": "4.29.0" }, "suggest": { "symfony/console": "For helpful console commands such as SQL execution and import of files." @@ -467,7 +467,7 @@ ], "support": { "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/3.4.5" + "source": "https://github.com/doctrine/dbal/tree/3.4.6" }, "funding": [ { @@ -483,7 +483,7 @@ "type": "tidelift" } ], - "time": "2022-09-23T17:48:57+00:00" + "time": "2022-10-21T14:38:43+00:00" }, { "name": "doctrine/deprecations", @@ -622,23 +622,23 @@ }, { "name": "doctrine/inflector", - "version": "2.0.5", + "version": "2.0.6", "source": { "type": "git", "url": "https://github.com/doctrine/inflector.git", - "reference": "ade2b3bbfb776f27f0558e26eed43b5d9fe1b392" + "reference": "d9d313a36c872fd6ee06d9a6cbcf713eaa40f024" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/inflector/zipball/ade2b3bbfb776f27f0558e26eed43b5d9fe1b392", - "reference": "ade2b3bbfb776f27f0558e26eed43b5d9fe1b392", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/d9d313a36c872fd6ee06d9a6cbcf713eaa40f024", + "reference": "d9d313a36c872fd6ee06d9a6cbcf713eaa40f024", "shasum": "" }, "require": { "php": "^7.2 || ^8.0" }, "require-dev": { - "doctrine/coding-standard": "^9", + "doctrine/coding-standard": "^10", "phpstan/phpstan": "^1.8", "phpstan/phpstan-phpunit": "^1.1", "phpstan/phpstan-strict-rules": "^1.3", @@ -693,7 +693,7 @@ ], "support": { "issues": "https://github.com/doctrine/inflector/issues", - "source": "https://github.com/doctrine/inflector/tree/2.0.5" + "source": "https://github.com/doctrine/inflector/tree/2.0.6" }, "funding": [ { @@ -709,7 +709,7 @@ "type": "tidelift" } ], - "time": "2022-09-07T09:01:28+00:00" + "time": "2022-10-20T09:10:12+00:00" }, { "name": "doctrine/lexer", @@ -1263,16 +1263,16 @@ }, { "name": "guzzlehttp/psr7", - "version": "2.4.1", + "version": "2.4.2", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "69568e4293f4fa993f3b0e51c9723e1e17c41379" + "reference": "3148458748274be1546f8f2809a6c09fe66f44aa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/69568e4293f4fa993f3b0e51c9723e1e17c41379", - "reference": "69568e4293f4fa993f3b0e51c9723e1e17c41379", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/3148458748274be1546f8f2809a6c09fe66f44aa", + "reference": "3148458748274be1546f8f2809a6c09fe66f44aa", "shasum": "" }, "require": { @@ -1362,7 +1362,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.4.1" + "source": "https://github.com/guzzle/psr7/tree/2.4.2" }, "funding": [ { @@ -1378,7 +1378,7 @@ "type": "tidelift" } ], - "time": "2022-08-28T14:45:39+00:00" + "time": "2022-10-25T13:49:28+00:00" }, { "name": "hashids/hashids", @@ -1513,16 +1513,16 @@ }, { "name": "laravel/framework", - "version": "v9.36.3", + "version": "v9.37.0", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "80ba0561b3682b96743e1c152fde0698bbdb2412" + "reference": "0c9675abf6d966e834b2ebeca3319f524e07a330" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/80ba0561b3682b96743e1c152fde0698bbdb2412", - "reference": "80ba0561b3682b96743e1c152fde0698bbdb2412", + "url": "https://api.github.com/repos/laravel/framework/zipball/0c9675abf6d966e834b2ebeca3319f524e07a330", + "reference": "0c9675abf6d966e834b2ebeca3319f524e07a330", "shasum": "" }, "require": { @@ -1534,7 +1534,7 @@ "fruitcake/php-cors": "^1.2", "laravel/serializable-closure": "^1.2.2", "league/commonmark": "^2.2", - "league/flysystem": "^3.0.16", + "league/flysystem": "^3.8.0", "monolog/monolog": "^2.0", "nesbot/carbon": "^2.62.1", "nunomaduro/termwind": "^1.13", @@ -1695,7 +1695,7 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2022-10-19T13:23:53+00:00" + "time": "2022-10-25T15:43:46+00:00" }, { "name": "laravel/helpers", @@ -2332,16 +2332,16 @@ }, { "name": "league/flysystem", - "version": "3.9.0", + "version": "3.10.2", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "60f3760352fe08e918bc3b1acae4e91af092ebe1" + "reference": "b9bd194b016114d6ff6765c09d40c7d427e4e3f6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/60f3760352fe08e918bc3b1acae4e91af092ebe1", - "reference": "60f3760352fe08e918bc3b1acae4e91af092ebe1", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/b9bd194b016114d6ff6765c09d40c7d427e4e3f6", + "reference": "b9bd194b016114d6ff6765c09d40c7d427e4e3f6", "shasum": "" }, "require": { @@ -2357,7 +2357,7 @@ }, "require-dev": { "async-aws/s3": "^1.5", - "async-aws/simple-s3": "^1.0", + "async-aws/simple-s3": "^1.1", "aws/aws-sdk-php": "^3.198.1", "composer/semver": "^3.0", "ext-fileinfo": "*", @@ -2403,7 +2403,7 @@ ], "support": { "issues": "https://github.com/thephpleague/flysystem/issues", - "source": "https://github.com/thephpleague/flysystem/tree/3.9.0" + "source": "https://github.com/thephpleague/flysystem/tree/3.10.2" }, "funding": [ { @@ -2419,7 +2419,7 @@ "type": "tidelift" } ], - "time": "2022-10-18T21:02:43+00:00" + "time": "2022-10-25T07:01:47+00:00" }, { "name": "league/flysystem-aws-s3-v3", @@ -3476,16 +3476,16 @@ }, { "name": "phpseclib/phpseclib", - "version": "3.0.16", + "version": "3.0.17", "source": { "type": "git", "url": "https://github.com/phpseclib/phpseclib.git", - "reference": "7181378909ed8890be4db53d289faac5b77f8b05" + "reference": "dbc2307d5c69aeb22db136c52e91130d7f2ca761" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/7181378909ed8890be4db53d289faac5b77f8b05", - "reference": "7181378909ed8890be4db53d289faac5b77f8b05", + "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/dbc2307d5c69aeb22db136c52e91130d7f2ca761", + "reference": "dbc2307d5c69aeb22db136c52e91130d7f2ca761", "shasum": "" }, "require": { @@ -3566,7 +3566,7 @@ ], "support": { "issues": "https://github.com/phpseclib/phpseclib/issues", - "source": "https://github.com/phpseclib/phpseclib/tree/3.0.16" + "source": "https://github.com/phpseclib/phpseclib/tree/3.0.17" }, "funding": [ { @@ -3582,7 +3582,7 @@ "type": "tidelift" } ], - "time": "2022-09-05T18:03:08+00:00" + "time": "2022-10-24T10:51:50+00:00" }, { "name": "pragmarx/google2fa", @@ -8725,6 +8725,66 @@ ], "time": "2022-10-19T21:46:50+00:00" }, + { + "name": "laravel/sail", + "version": "v1.16.2", + "source": { + "type": "git", + "url": "https://github.com/laravel/sail.git", + "reference": "7d1ed5f856ec8b9708712e3fc0708fcabe114659" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/sail/zipball/7d1ed5f856ec8b9708712e3fc0708fcabe114659", + "reference": "7d1ed5f856ec8b9708712e3fc0708fcabe114659", + "shasum": "" + }, + "require": { + "illuminate/console": "^8.0|^9.0", + "illuminate/contracts": "^8.0|^9.0", + "illuminate/support": "^8.0|^9.0", + "php": "^7.3|^8.0" + }, + "bin": [ + "bin/sail" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + }, + "laravel": { + "providers": [ + "Laravel\\Sail\\SailServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Laravel\\Sail\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Docker files for running a basic Laravel application.", + "keywords": [ + "docker", + "laravel" + ], + "support": { + "issues": "https://github.com/laravel/sail/issues", + "source": "https://github.com/laravel/sail" + }, + "time": "2022-09-28T13:13:22+00:00" + }, { "name": "mockery/mockery", "version": "1.5.1", @@ -10954,16 +11014,16 @@ }, { "name": "spatie/laravel-ignition", - "version": "1.5.2", + "version": "1.6.0", "source": { "type": "git", "url": "https://github.com/spatie/laravel-ignition.git", - "reference": "f2336fc79d99aab5cf27fa4aebe5e9c9ecf3808a" + "reference": "c21309ebf6657e0c38083afac8af9baa12885676" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-ignition/zipball/f2336fc79d99aab5cf27fa4aebe5e9c9ecf3808a", - "reference": "f2336fc79d99aab5cf27fa4aebe5e9c9ecf3808a", + "url": "https://api.github.com/repos/spatie/laravel-ignition/zipball/c21309ebf6657e0c38083afac8af9baa12885676", + "reference": "c21309ebf6657e0c38083afac8af9baa12885676", "shasum": "" }, "require": { @@ -11040,7 +11100,7 @@ "type": "github" } ], - "time": "2022-10-14T12:24:21+00:00" + "time": "2022-10-25T08:38:04+00:00" }, { "name": "symfony/filesystem", @@ -11291,7 +11351,7 @@ "prefer-stable": true, "prefer-lowest": false, "platform": { - "php": "^8.0.2 || ^8.1", + "php": "^8.0.2 || ^8.1 || ^8.2", "ext-json": "*", "ext-mbstring": "*", "ext-pdo": "*", diff --git a/phpunit.xml b/phpunit.xml index d02831cf4c..efc42d687c 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -22,7 +22,7 @@ - + From c068f57e4e3d8fb6e25d504edec87404fb525330 Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 7 Nov 2022 00:15:12 +0200 Subject: [PATCH 267/458] fix(forge): validate only input and not length (#4528) Only allows digits, dots and dashes in the input, which is what Forge versions consists of. Removes arbitrary char limit. --- database/Seeders/eggs/minecraft/egg-forge-minecraft.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/database/Seeders/eggs/minecraft/egg-forge-minecraft.json b/database/Seeders/eggs/minecraft/egg-forge-minecraft.json index fbb097f068..189cafad98 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_v2", "update_url": null }, - "exported_at": "2022-06-17T08:11:14+03:00", + "exported_at": "2022-11-06T06:33:01-05: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.", @@ -67,12 +67,12 @@ }, { "name": "Forge Version", - "description": "Gets an exact version.\r\n\r\nEx. 1.15.2-31.2.4\r\n\r\nOverrides MC_VERSION and BUILD_TYPE. If it fails to download the server files it will fail to install.", + "description": "The full exact version.\r\n\r\nEx. 1.15.2-31.2.4\r\n\r\nOverrides MC_VERSION and BUILD_TYPE. If it fails to download the server files it will fail to install.", "env_variable": "FORGE_VERSION", "default_value": "", "user_viewable": true, "user_editable": true, - "rules": "nullable|string|max:25", + "rules": "nullable|regex:\/^[0-9\\.\\-]+$\/", "field_type": "text" } ] From 9b218f219062fedf4ff318ebe966da10e8421f79 Mon Sep 17 00:00:00 2001 From: Lance Pioch Date: Sun, 6 Nov 2022 17:24:33 -0500 Subject: [PATCH 268/458] URL encode password in JDBC connection string (#4527) --- resources/scripts/components/server/databases/DatabaseRow.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/scripts/components/server/databases/DatabaseRow.tsx b/resources/scripts/components/server/databases/DatabaseRow.tsx index 1b486959ed..9d2c05a73c 100644 --- a/resources/scripts/components/server/databases/DatabaseRow.tsx +++ b/resources/scripts/components/server/databases/DatabaseRow.tsx @@ -35,7 +35,7 @@ export default ({ database, className }: Props) => { const removeDatabase = ServerContext.useStoreActions((actions) => actions.databases.removeDatabase); const jdbcConnectionString = `jdbc:mysql://${database.username}${ - database.password ? `:${database.password}` : '' + database.password ? `:${encodeURIComponent(database.password)}` : '' }@${database.connectionString}/${database.name}`; const schema = object().shape({ From 4032481a4f2f62e34b12f2e33f462559f73776de Mon Sep 17 00:00:00 2001 From: Lance Pioch Date: Sun, 6 Nov 2022 17:42:48 -0500 Subject: [PATCH 269/458] Update validation rules for remote activity logs (#4526) --- app/Http/Requests/Api/Remote/ActivityEventRequest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Http/Requests/Api/Remote/ActivityEventRequest.php b/app/Http/Requests/Api/Remote/ActivityEventRequest.php index 32ad11ad3f..43f527dfa6 100644 --- a/app/Http/Requests/Api/Remote/ActivityEventRequest.php +++ b/app/Http/Requests/Api/Remote/ActivityEventRequest.php @@ -17,11 +17,11 @@ public function rules(): array return [ 'data' => ['required', 'array'], 'data.*' => ['array'], - 'data.*.user' => ['present', 'uuid'], + 'data.*.user' => ['sometimes', 'nullable', 'uuid'], 'data.*.server' => ['required', 'uuid'], 'data.*.event' => ['required', 'string'], 'data.*.metadata' => ['present', 'nullable', 'array'], - 'data.*.ip' => ['present', 'ip'], + 'data.*.ip' => ['sometimes', 'nullable', 'ip'], 'data.*.timestamp' => ['required', 'string'], ]; } From 032e4f2e31a9de60be734702d1f158ef8d07ebf6 Mon Sep 17 00:00:00 2001 From: Boy132 Date: Mon, 7 Nov 2022 00:02:30 +0100 Subject: [PATCH 270/458] Apply node maintenance mode to servers (#4421) --- .../Http/Server/ServerStateConflictException.php | 2 ++ .../Api/Client/Server/AuthenticateServerAccess.php | 2 +- .../Requests/Api/Application/Nodes/StoreNodeRequest.php | 1 + app/Models/Node.php | 5 +++++ app/Models/Server.php | 1 + app/Transformers/Api/Client/ServerTransformer.php | 1 + resources/scripts/api/server/getServer.ts | 2 ++ .../scripts/components/server/ConflictStateRenderer.tsx | 9 +++++++++ .../components/server/console/ServerConsoleContainer.tsx | 7 +++++-- resources/scripts/state/server/index.ts | 2 +- resources/views/admin/nodes/view/index.blade.php | 2 +- 11 files changed, 29 insertions(+), 5 deletions(-) diff --git a/app/Exceptions/Http/Server/ServerStateConflictException.php b/app/Exceptions/Http/Server/ServerStateConflictException.php index f0eb096b16..ea6a60a559 100644 --- a/app/Exceptions/Http/Server/ServerStateConflictException.php +++ b/app/Exceptions/Http/Server/ServerStateConflictException.php @@ -17,6 +17,8 @@ public function __construct(Server $server, Throwable $previous = null) $message = 'This server is currently in an unsupported state, please try again later.'; if ($server->isSuspended()) { $message = 'This server is currently suspended and the functionality requested is unavailable.'; + } elseif ($server->node->isUnderMaintenance()) { + $message = 'The node of this server is currently under maintenance and the functionality requested is unavailable.'; } elseif (!$server->isInstalled()) { $message = 'This server has not yet completed its installation process, please try again later.'; } elseif ($server->status === Server::STATUS_RESTORING_BACKUP) { diff --git a/app/Http/Middleware/Api/Client/Server/AuthenticateServerAccess.php b/app/Http/Middleware/Api/Client/Server/AuthenticateServerAccess.php index 40a4d0cf1c..358fb6d572 100644 --- a/app/Http/Middleware/Api/Client/Server/AuthenticateServerAccess.php +++ b/app/Http/Middleware/Api/Client/Server/AuthenticateServerAccess.php @@ -53,7 +53,7 @@ public function handle(Request $request, Closure $next): mixed // Still allow users to get information about their server if it is installing or // being transferred. if (!$request->routeIs('api:client:server.view')) { - if ($server->isSuspended() && !$request->routeIs('api:client:server.resources')) { + if (($server->isSuspended() || $server->node->isUnderMaintenance()) && !$request->routeIs('api:client:server.resources')) { throw $exception; } if (!$user->root_admin || !$request->routeIs($this->except)) { diff --git a/app/Http/Requests/Api/Application/Nodes/StoreNodeRequest.php b/app/Http/Requests/Api/Application/Nodes/StoreNodeRequest.php index bc559083e5..4fe7054485 100644 --- a/app/Http/Requests/Api/Application/Nodes/StoreNodeRequest.php +++ b/app/Http/Requests/Api/Application/Nodes/StoreNodeRequest.php @@ -24,6 +24,7 @@ public function rules(array $rules = null): array 'fqdn', 'scheme', 'behind_proxy', + 'maintenance_mode', 'memory', 'memory_overallocate', 'disk', diff --git a/app/Models/Node.php b/app/Models/Node.php index 37aec760d6..62ec828712 100644 --- a/app/Models/Node.php +++ b/app/Models/Node.php @@ -186,6 +186,11 @@ public function getDecryptedKey(): string ); } + public function isUnderMaintenance(): bool + { + return $this->maintenance_mode; + } + public function mounts(): HasManyThrough { return $this->hasManyThrough(Mount::class, MountNode::class, 'node_id', 'id', 'id', 'mount_id'); diff --git a/app/Models/Server.php b/app/Models/Server.php index 5ad99151a3..d7cc649c09 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -354,6 +354,7 @@ public function validateCurrentState() { if ( $this->isSuspended() || + $this->node->isUnderMaintenance() || !$this->isInstalled() || $this->status === self::STATUS_RESTORING_BACKUP || !is_null($this->transfer) diff --git a/app/Transformers/Api/Client/ServerTransformer.php b/app/Transformers/Api/Client/ServerTransformer.php index 8ae4ed4a6a..9f7bce958a 100644 --- a/app/Transformers/Api/Client/ServerTransformer.php +++ b/app/Transformers/Api/Client/ServerTransformer.php @@ -43,6 +43,7 @@ public function transform(Server $server): array 'uuid' => $server->uuid, 'name' => $server->name, 'node' => $server->node->name, + 'is_node_under_maintenance' => $server->node->isUnderMaintenance(), 'sftp_details' => [ 'ip' => $server->node->fqdn, 'port' => $server->node->daemonSFTP, diff --git a/resources/scripts/api/server/getServer.ts b/resources/scripts/api/server/getServer.ts index 5ed92a4270..d2aa2e0543 100644 --- a/resources/scripts/api/server/getServer.ts +++ b/resources/scripts/api/server/getServer.ts @@ -17,6 +17,7 @@ export interface Server { uuid: string; name: string; node: string; + isNodeUnderMaintenance: boolean; status: ServerStatus; sftpDetails: { ip: string; @@ -50,6 +51,7 @@ export const rawDataToServerObject = ({ attributes: data }: FractalResponseData) uuid: data.uuid, name: data.name, node: data.node, + isNodeUnderMaintenance: data.is_node_under_maintenance, status: data.status, invocation: data.invocation, dockerImage: data.docker_image, diff --git a/resources/scripts/components/server/ConflictStateRenderer.tsx b/resources/scripts/components/server/ConflictStateRenderer.tsx index 70475a4eef..95e70bbaa3 100644 --- a/resources/scripts/components/server/ConflictStateRenderer.tsx +++ b/resources/scripts/components/server/ConflictStateRenderer.tsx @@ -8,6 +8,9 @@ import ServerRestoreSvg from '@/assets/images/server_restore.svg'; export default () => { const status = ServerContext.useStoreState((state) => state.server.data?.status || null); const isTransferring = ServerContext.useStoreState((state) => state.server.data?.isTransferring || false); + const isNodeUnderMaintenance = ServerContext.useStoreState( + (state) => state.server.data?.isNodeUnderMaintenance || false + ); return status === 'installing' || status === 'install_failed' ? ( { image={ServerErrorSvg} message={'This server is suspended and cannot be accessed.'} /> + ) : isNodeUnderMaintenance ? ( + ) : ( { const isInstalling = ServerContext.useStoreState((state) => state.server.isInstalling); const isTransferring = ServerContext.useStoreState((state) => state.server.data!.isTransferring); const eggFeatures = ServerContext.useStoreState((state) => state.server.data!.eggFeatures, isEqual); + const isNodeUnderMaintenance = ServerContext.useStoreState((state) => state.server.data!.isNodeUnderMaintenance); return ( - {(isInstalling || isTransferring) && ( + {(isNodeUnderMaintenance || isInstalling || isTransferring) && ( - {isInstalling + {isNodeUnderMaintenance + ? 'The node of this server is currently under maintenance and all actions are unavailable.' + : isInstalling ? 'This server is currently running its installation process and most actions are unavailable.' : 'This server is currently being transferred to another node and all actions are unavailable.'} diff --git a/resources/scripts/state/server/index.ts b/resources/scripts/state/server/index.ts index 8d8eba749f..f9806b6498 100644 --- a/resources/scripts/state/server/index.ts +++ b/resources/scripts/state/server/index.ts @@ -30,7 +30,7 @@ const server: ServerDataStore = { return false; } - return state.data.status !== null || state.data.isTransferring; + return state.data.status !== null || state.data.isTransferring || state.data.isNodeUnderMaintenance; }), isInstalling: computed((state) => { diff --git a/resources/views/admin/nodes/view/index.blade.php b/resources/views/admin/nodes/view/index.blade.php index 9ef461076a..ce90b30b2d 100644 --- a/resources/views/admin/nodes/view/index.blade.php +++ b/resources/views/admin/nodes/view/index.blade.php @@ -93,7 +93,7 @@
@if($node->maintenance_mode)
-
+
This node is under From df2402b54f8db10e8319b4250a2960c97e22b296 Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Mon, 14 Nov 2022 18:25:07 -0700 Subject: [PATCH 271/458] Streaming Transfers (#4548) --- .../Servers/ServerTransferController.php | 42 ++++++++++------ .../Servers/ServerTransferController.php | 48 +------------------ .../Wings/DaemonTransferRepository.php | 12 ++--- app/Services/Servers/TransferService.php | 27 ----------- .../components/server/TransferListener.tsx | 8 ++-- .../components/server/console/Console.tsx | 7 --- 6 files changed, 38 insertions(+), 106 deletions(-) delete mode 100644 app/Services/Servers/TransferService.php diff --git a/app/Http/Controllers/Admin/Servers/ServerTransferController.php b/app/Http/Controllers/Admin/Servers/ServerTransferController.php index 0962174186..8941ce10c5 100644 --- a/app/Http/Controllers/Admin/Servers/ServerTransferController.php +++ b/app/Http/Controllers/Admin/Servers/ServerTransferController.php @@ -2,15 +2,17 @@ namespace Pterodactyl\Http\Controllers\Admin\Servers; +use Carbon\CarbonImmutable; use Illuminate\Http\Request; use Pterodactyl\Models\Server; use Illuminate\Http\RedirectResponse; use Prologue\Alerts\AlertsMessageBag; use Pterodactyl\Models\ServerTransfer; +use Illuminate\Database\ConnectionInterface; use Pterodactyl\Http\Controllers\Controller; -use Pterodactyl\Services\Servers\TransferService; +use Pterodactyl\Services\Nodes\NodeJWTService; use Pterodactyl\Repositories\Eloquent\NodeRepository; -use Pterodactyl\Repositories\Wings\DaemonConfigurationRepository; +use Pterodactyl\Repositories\Wings\DaemonTransferRepository; use Pterodactyl\Contracts\Repository\AllocationRepositoryInterface; class ServerTransferController extends Controller @@ -21,9 +23,10 @@ class ServerTransferController extends Controller public function __construct( private AlertsMessageBag $alert, private AllocationRepositoryInterface $allocationRepository, - private NodeRepository $nodeRepository, - private TransferService $transferService, - private DaemonConfigurationRepository $daemonConfigurationRepository + private ConnectionInterface $connection, + private DaemonTransferRepository $daemonTransferRepository, + private NodeJWTService $nodeJWTService, + private NodeRepository $nodeRepository ) { } @@ -46,12 +49,15 @@ public function transfer(Request $request, Server $server): RedirectResponse // Check if the node is viable for the transfer. $node = $this->nodeRepository->getNodeWithResourceUsage($node_id); - if ($node->isViable($server->memory, $server->disk)) { - // Check if the selected daemon is online. - $this->daemonConfigurationRepository->setNode($node)->getSystemInformation(); + if (!$node->isViable($server->memory, $server->disk)) { + $this->alert->danger(trans('admin/server.alerts.transfer_not_viable'))->flash(); + + return redirect()->route('admin.servers.view.manage', $server->id); + } - $server->validateTransferState(); + $server->validateTransferState(); + $this->connection->transaction(function () use ($server, $node_id, $allocation_id, $additional_allocations) { // Create a new ServerTransfer entry. $transfer = new ServerTransfer(); @@ -68,13 +74,19 @@ public function transfer(Request $request, Server $server): RedirectResponse // Add the allocations to the server, so they cannot be automatically assigned while the transfer is in progress. $this->assignAllocationsToServer($server, $node_id, $allocation_id, $additional_allocations); - // Request an archive from the server's current daemon. (this also checks if the daemon is online) - $this->transferService->requestArchive($server); + // Generate a token for the destination node that the source node can use to authenticate with. + $token = $this->nodeJWTService + ->setExpiresAt(CarbonImmutable::now()->addMinutes(15)) + ->setSubject($server->uuid) + ->handle($transfer->newNode, $server->uuid, 'sha256'); - $this->alert->success(trans('admin/server.alerts.transfer_started'))->flash(); - } else { - $this->alert->danger(trans('admin/server.alerts.transfer_not_viable'))->flash(); - } + // Notify the source node of the pending outgoing transfer. + $this->daemonTransferRepository->setServer($server)->notify($transfer->newNode, $token); + + return $transfer; + }); + + $this->alert->success(trans('admin/server.alerts.transfer_started'))->flash(); return redirect()->route('admin.servers.view.manage', $server->id); } diff --git a/app/Http/Controllers/Api/Remote/Servers/ServerTransferController.php b/app/Http/Controllers/Api/Remote/Servers/ServerTransferController.php index 6f49d66e01..72153bf252 100644 --- a/app/Http/Controllers/Api/Remote/Servers/ServerTransferController.php +++ b/app/Http/Controllers/Api/Remote/Servers/ServerTransferController.php @@ -2,8 +2,6 @@ namespace Pterodactyl\Http\Controllers\Api\Remote\Servers; -use Carbon\CarbonImmutable; -use Illuminate\Http\Request; use Illuminate\Http\Response; use Illuminate\Http\JsonResponse; use Pterodactyl\Models\Allocation; @@ -11,10 +9,8 @@ use Pterodactyl\Models\ServerTransfer; use Illuminate\Database\ConnectionInterface; use Pterodactyl\Http\Controllers\Controller; -use Pterodactyl\Services\Nodes\NodeJWTService; use Pterodactyl\Repositories\Eloquent\ServerRepository; use Pterodactyl\Repositories\Wings\DaemonServerRepository; -use Pterodactyl\Repositories\Wings\DaemonTransferRepository; use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException; class ServerTransferController extends Controller @@ -25,52 +21,10 @@ class ServerTransferController extends Controller public function __construct( private ConnectionInterface $connection, private ServerRepository $repository, - private DaemonServerRepository $daemonServerRepository, - private DaemonTransferRepository $daemonTransferRepository, - private NodeJWTService $jwtService + private DaemonServerRepository $daemonServerRepository ) { } - /** - * The daemon notifies us about the archive status. - * - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - * @throws \Throwable - */ - public function archive(Request $request, string $uuid): JsonResponse - { - $server = $this->repository->getByUuid($uuid); - - // Unsuspend the server and don't continue the transfer. - if (!$request->input('successful')) { - return $this->processFailedTransfer($server->transfer); - } - - $this->connection->transaction(function () use ($server) { - // This token is used by the new node the server is being transferred to. It allows - // that node to communicate with the old node during the process to initiate the - // actual file transfer. - $token = $this->jwtService - ->setExpiresAt(CarbonImmutable::now()->addMinutes(15)) - ->setSubject($server->uuid) - ->handle($server->node, $server->uuid, 'sha256'); - - // Update the archived field on the transfer to make clients connect to the websocket - // on the new node to be able to receive transfer logs. - $server->transfer->forceFill(['archived' => true])->saveOrFail(); - - // On the daemon transfer repository, make sure to set the node after the server - // because setServer() tells the repository to use the server's node and not the one - // we want to specify. - $this->daemonTransferRepository - ->setServer($server) - ->setNode($server->transfer->newNode) - ->notify($server, $token); - }); - - return new JsonResponse([], Response::HTTP_NO_CONTENT); - } - /** * The daemon notifies us about a transfer failure. * diff --git a/app/Repositories/Wings/DaemonTransferRepository.php b/app/Repositories/Wings/DaemonTransferRepository.php index 3939a47cd1..9c8745232b 100644 --- a/app/Repositories/Wings/DaemonTransferRepository.php +++ b/app/Repositories/Wings/DaemonTransferRepository.php @@ -2,8 +2,8 @@ namespace Pterodactyl\Repositories\Wings; +use Pterodactyl\Models\Node; use Lcobucci\JWT\Token\Plain; -use Pterodactyl\Models\Server; use GuzzleHttp\Exception\GuzzleException; use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException; @@ -12,16 +12,16 @@ class DaemonTransferRepository extends DaemonRepository /** * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException */ - public function notify(Server $server, Plain $token): void + public function notify(Node $targetNode, Plain $token): void { try { - $this->getHttpClient()->post('/api/transfer', [ + $this->getHttpClient()->post(sprintf('/api/servers/%s/transfer', $this->server->uuid), [ 'json' => [ - 'server_id' => $server->uuid, - 'url' => $server->node->getConnectionAddress() . sprintf('/api/servers/%s/archive', $server->uuid), + 'server_id' => $this->server->uuid, + 'url' => $targetNode->getConnectionAddress() . '/api/transfers', 'token' => 'Bearer ' . $token->toString(), 'server' => [ - 'uuid' => $server->uuid, + 'uuid' => $this->server->uuid, 'start_on_completion' => false, ], ], diff --git a/app/Services/Servers/TransferService.php b/app/Services/Servers/TransferService.php deleted file mode 100644 index 24ef0a5881..0000000000 --- a/app/Services/Servers/TransferService.php +++ /dev/null @@ -1,27 +0,0 @@ -daemonServerRepository->setServer($server)->requestArchive(); - } -} diff --git a/resources/scripts/components/server/TransferListener.tsx b/resources/scripts/components/server/TransferListener.tsx index 64460aa699..4d94217453 100644 --- a/resources/scripts/components/server/TransferListener.tsx +++ b/resources/scripts/components/server/TransferListener.tsx @@ -7,19 +7,19 @@ const TransferListener = () => { const getServer = ServerContext.useStoreActions((actions) => actions.server.getServer); const setServerFromState = ServerContext.useStoreActions((actions) => actions.server.setServerFromState); - // Listen for the transfer status event so we can update the state of the server. + // Listen for the transfer status event, so we can update the state of the server. useWebsocketEvent(SocketEvent.TRANSFER_STATUS, (status: string) => { - if (status === 'starting') { + if (status === 'pending' || status === 'processing') { setServerFromState((s) => ({ ...s, isTransferring: true })); return; } - if (status === 'failure') { + if (status === 'failed') { setServerFromState((s) => ({ ...s, isTransferring: false })); return; } - if (status !== 'success') { + if (status !== 'completed') { return; } diff --git a/resources/scripts/components/server/console/Console.tsx b/resources/scripts/components/server/console/Console.tsx index 9468d0a56a..e3cb43ab26 100644 --- a/resources/scripts/components/server/console/Console.tsx +++ b/resources/scripts/components/server/console/Console.tsx @@ -76,13 +76,6 @@ export default () => { case 'failure': terminal.writeln(TERMINAL_PRELUDE + 'Transfer has failed.\u001b[0m'); return; - - // Sent by the source node whenever the server was archived successfully. - case 'archive': - terminal.writeln( - TERMINAL_PRELUDE + - 'Server has been archived successfully, attempting connection to target node..\u001b[0m' - ); } }; From 897ca48ded10743febb0e1f3895ae7429a17ff8d Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Mon, 14 Nov 2022 18:28:02 -0700 Subject: [PATCH 272/458] github: re-enable blank issues --- .github/ISSUE_TEMPLATE/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 09011627c5..fee5ad9487 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,4 +1,4 @@ -blank_issues_enabled: false +blank_issues_enabled: true contact_links: - name: Installation Help url: https://discord.gg/pterodactyl From c1584d9a5bc8e8a284ddd7d3de7e38015401651b Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Mon, 14 Nov 2022 20:31:03 -0700 Subject: [PATCH 273/458] Update CHANGELOG.md --- CHANGELOG.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cf74334192..371916bc96 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,10 +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. -## [Unreleased] +## v1.11.0-rc.1 ### Changed * Changed minimum PHP version is now 8.0 instead of `7.4`. * Upgraded from Laravel 8 to Laravel 9. +* This release requires Wings v1.11.x in order for Server Transfers to work. + +### Fixed +* Node maintenance mode now properly blocks access to servers. +* Fixed the length validation on the Minecraft Forge egg. +* Fixed the password in the JDBC string not being properly URL encoded. +* Fixed an issue where Wings would throw a validation error while attempting to upload activity logs. ## v1.10.4 ### Fixed From 2f4a60c9615af0f06b025c3936ade89dc87ba974 Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Mon, 21 Nov 2022 13:10:00 -0700 Subject: [PATCH 274/458] ui(admin): change `MB` suffixes to `MiB` Closes #4542 --- resources/views/admin/nodes/index.blade.php | 4 ++-- resources/views/admin/nodes/new.blade.php | 4 ++-- resources/views/admin/nodes/view/index.blade.php | 4 ++-- resources/views/admin/nodes/view/settings.blade.php | 6 +++--- resources/views/admin/servers/new.blade.php | 6 +++--- resources/views/admin/servers/view/build.blade.php | 6 +++--- resources/views/admin/servers/view/index.blade.php | 4 ++-- 7 files changed, 17 insertions(+), 17 deletions(-) diff --git a/resources/views/admin/nodes/index.blade.php b/resources/views/admin/nodes/index.blade.php index a4747b3fcd..3b3a3fa48f 100644 --- a/resources/views/admin/nodes/index.blade.php +++ b/resources/views/admin/nodes/index.blade.php @@ -53,8 +53,8 @@ {!! $node->maintenance_mode ? ' ' : '' !!}{{ $node->name }} {{ $node->location->short }} - {{ $node->memory }} MB - {{ $node->disk }} MB + {{ $node->memory }} MiB + {{ $node->disk }} MiB {{ $node->servers_count }} diff --git a/resources/views/admin/nodes/new.blade.php b/resources/views/admin/nodes/new.blade.php index 3e10be5a14..b10d08a762 100644 --- a/resources/views/admin/nodes/new.blade.php +++ b/resources/views/admin/nodes/new.blade.php @@ -110,7 +110,7 @@
- MB + MiB
@@ -129,7 +129,7 @@
- MB + MiB
diff --git a/resources/views/admin/nodes/view/index.blade.php b/resources/views/admin/nodes/view/index.blade.php index ce90b30b2d..2d0bb32874 100644 --- a/resources/views/admin/nodes/view/index.blade.php +++ b/resources/views/admin/nodes/view/index.blade.php @@ -107,7 +107,7 @@
Disk Space Allocated - {{ $stats['disk']['value'] }} / {{ $stats['disk']['max'] }} MB + {{ $stats['disk']['value'] }} / {{ $stats['disk']['max'] }} MiB
@@ -119,7 +119,7 @@
Memory Allocated - {{ $stats['memory']['value'] }} / {{ $stats['memory']['max'] }} MB + {{ $stats['memory']['value'] }} / {{ $stats['memory']['max'] }} MiB
diff --git a/resources/views/admin/nodes/view/settings.blade.php b/resources/views/admin/nodes/view/settings.blade.php index e4848aca34..bfc9d8caf7 100644 --- a/resources/views/admin/nodes/view/settings.blade.php +++ b/resources/views/admin/nodes/view/settings.blade.php @@ -132,7 +132,7 @@
- MB + MiB
@@ -151,7 +151,7 @@
- MB + MiB
@@ -177,7 +177,7 @@
- MB + MiB

Enter the maximum size of files that can be uploaded through the web-based file manager.

diff --git a/resources/views/admin/servers/new.blade.php b/resources/views/admin/servers/new.blade.php index bbe779ce6c..4198634f1e 100644 --- a/resources/views/admin/servers/new.blade.php +++ b/resources/views/admin/servers/new.blade.php @@ -170,7 +170,7 @@
- MB + MiB

The maximum amount of memory allowed for this container. Setting this to 0 will allow unlimited memory in a container.

@@ -181,7 +181,7 @@
- MB + MiB

Setting this to 0 will disable swap space on this server. Setting to -1 will allow unlimited swap.

@@ -194,7 +194,7 @@
- MB + MiB

This server will not be allowed to boot if it is using more than this amount of space. If a server goes over this limit while running it will be safely stopped and locked until enough space is available. Set to 0 to allow unlimited disk usage.

diff --git a/resources/views/admin/servers/view/build.blade.php b/resources/views/admin/servers/view/build.blade.php index f7ba9a2d2a..655ea36af3 100644 --- a/resources/views/admin/servers/view/build.blade.php +++ b/resources/views/admin/servers/view/build.blade.php @@ -43,7 +43,7 @@
- MB + MiB

The maximum amount of memory allowed for this container. Setting this to 0 will allow unlimited memory in a container.

@@ -51,7 +51,7 @@
- MB + MiB

Setting this to 0 will disable swap space on this server. Setting to -1 will allow unlimited swap.

@@ -59,7 +59,7 @@
- MB + MiB

This server will not be allowed to boot if it is using more than this amount of space. If a server goes over this limit while running it will be safely stopped and locked until enough space is available. Set to 0 to allow unlimited disk usage.

diff --git a/resources/views/admin/servers/view/index.blade.php b/resources/views/admin/servers/view/index.blade.php index e9b95f9856..f94c6d42fc 100644 --- a/resources/views/admin/servers/view/index.blade.php +++ b/resources/views/admin/servers/view/index.blade.php @@ -78,7 +78,7 @@ @if($server->memory === 0) Unlimited @else - {{ $server->memory }}MB + {{ $server->memory }}MiB @endif / @if($server->swap === 0) @@ -86,7 +86,7 @@ @elseif($server->swap === -1) Unlimited @else - {{ $server->swap }}MB + {{ $server->swap }}MiB @endif From c3521e022161606c115d8a900040cf2cae190a96 Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Mon, 21 Nov 2022 13:15:49 -0700 Subject: [PATCH 275/458] api(server): fix undefined header --- app/Repositories/Wings/DaemonFileRepository.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Repositories/Wings/DaemonFileRepository.php b/app/Repositories/Wings/DaemonFileRepository.php index eb36496412..3bd8978813 100644 --- a/app/Repositories/Wings/DaemonFileRepository.php +++ b/app/Repositories/Wings/DaemonFileRepository.php @@ -2,6 +2,7 @@ namespace Pterodactyl\Repositories\Wings; +use Illuminate\Support\Arr; use Webmozart\Assert\Assert; use Pterodactyl\Models\Server; use Psr\Http\Message\ResponseInterface; @@ -35,8 +36,7 @@ public function getContent(string $path, int $notLargerThan = null): string throw new DaemonConnectionException($exception); } - $length = (int) $response->getHeader('Content-Length')[0] ?? 0; - + $length = (int) Arr::get($response->getHeader('Content-Length'), 0, 0); if ($notLargerThan && $length > $notLargerThan) { throw new FileSizeTooLargeException(); } From 634c9353e309c603064530824dfbb3c870a1f7b6 Mon Sep 17 00:00:00 2001 From: Devonte W Date: Mon, 21 Nov 2022 20:28:46 +0000 Subject: [PATCH 276/458] fix(transformers): force object type for properties (#4544) --- app/Transformers/Api/Client/ActivityLogTransformer.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/Transformers/Api/Client/ActivityLogTransformer.php b/app/Transformers/Api/Client/ActivityLogTransformer.php index 849fdc865b..57c8ac30ed 100644 --- a/app/Transformers/Api/Client/ActivityLogTransformer.php +++ b/app/Transformers/Api/Client/ActivityLogTransformer.php @@ -47,10 +47,10 @@ public function includeActor(ActivityLog $model) * Transforms any array values in the properties into a countable field for easier * use within the translation outputs. */ - protected function properties(ActivityLog $model): array + protected function properties(ActivityLog $model): object { if (!$model->properties || $model->properties->isEmpty()) { - return []; + return (object) []; } $properties = $model->properties @@ -76,7 +76,7 @@ protected function properties(ActivityLog $model): array $properties = $properties->merge(['count' => $properties->get($keys[0])])->except($keys[0]); } - return $properties->toArray(); + return (object) $properties->toArray(); } /** From 039ad4abf04586011de484ce2942429d0ecbad75 Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Mon, 21 Nov 2022 13:41:26 -0700 Subject: [PATCH 277/458] api(server): log activity when server description is changed --- .../Api/Client/Servers/SettingsController.php | 16 ++++++++++++---- app/Services/Activity/ActivityLogService.php | 4 ++-- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/app/Http/Controllers/Api/Client/Servers/SettingsController.php b/app/Http/Controllers/Api/Client/Servers/SettingsController.php index d1377d67a3..6e64518368 100644 --- a/app/Http/Controllers/Api/Client/Servers/SettingsController.php +++ b/app/Http/Controllers/Api/Client/Servers/SettingsController.php @@ -34,14 +34,22 @@ public function __construct( */ public function rename(RenameServerRequest $request, Server $server): JsonResponse { + $name = $request->input('name'); + $description = $request->input('description') ?? ''; $this->repository->update($server->id, [ - 'name' => $request->input('name'), - 'description' => $request->input('description') ?? '', + 'name' => $name, + 'description' => $description, ]); - if ($server->name !== $request->input('name')) { + if ($server->name !== $name) { Activity::event('server:settings.rename') - ->property(['old' => $server->name, 'new' => $request->input('name')]) + ->property(['old' => $server->name, 'new' => $name]) + ->log(); + } + + if ($server->description !== $description) { + Activity::event('server:settings.description') + ->property(['old' => $server->description, 'new' => $description]) ->log(); } diff --git a/app/Services/Activity/ActivityLogService.php b/app/Services/Activity/ActivityLogService.php index fa4f469369..f863852149 100644 --- a/app/Services/Activity/ActivityLogService.php +++ b/app/Services/Activity/ActivityLogService.php @@ -99,7 +99,7 @@ public function actor(Model $actor): self } /** - * Sets a custom property on the activty log instance. + * Sets a custom property on the activity log instance. * * @param string|array $key * @param mixed $value @@ -115,7 +115,7 @@ public function property($key, $value = null): self } /** - * Attachs the instance request metadata to the activity log event. + * Attaches the instance request metadata to the activity log event. */ public function withRequestMetadata(): self { From a4f6870518f10855a063f19fc4dc69b9e83c070f Mon Sep 17 00:00:00 2001 From: Lance Pioch Date: Mon, 21 Nov 2022 15:53:54 -0500 Subject: [PATCH 278/458] server: track reinstall failures differently from initial install failures (#4531) --- .../Api/Remote/Servers/ServerInstallController.php | 12 +++++++++++- .../Requests/Api/Remote/InstallationDataRequest.php | 1 + app/Models/Server.php | 1 + 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/app/Http/Controllers/Api/Remote/Servers/ServerInstallController.php b/app/Http/Controllers/Api/Remote/Servers/ServerInstallController.php index e788437631..8fcfbe0643 100644 --- a/app/Http/Controllers/Api/Remote/Servers/ServerInstallController.php +++ b/app/Http/Controllers/Api/Remote/Servers/ServerInstallController.php @@ -48,8 +48,18 @@ public function index(Request $request, string $uuid): JsonResponse public function store(InstallationDataRequest $request, string $uuid): JsonResponse { $server = $this->repository->getByUuid($uuid); + $status = null; - $status = $request->boolean('successful') ? null : Server::STATUS_INSTALL_FAILED; + // Make sure the type of failure is accurate + if (!$request->boolean('successful')) { + $status = Server::STATUS_INSTALL_FAILED; + + if ($request->boolean('reinstall')) { + $status = Server::STATUS_REINSTALL_FAILED; + } + } + + // Keep the server suspended if it's already suspended if ($server->status === Server::STATUS_SUSPENDED) { $status = Server::STATUS_SUSPENDED; } diff --git a/app/Http/Requests/Api/Remote/InstallationDataRequest.php b/app/Http/Requests/Api/Remote/InstallationDataRequest.php index c5d1973ec4..13b3e77d7a 100644 --- a/app/Http/Requests/Api/Remote/InstallationDataRequest.php +++ b/app/Http/Requests/Api/Remote/InstallationDataRequest.php @@ -15,6 +15,7 @@ public function rules(): array { return [ 'successful' => 'present|boolean', + 'reinstall' => 'sometimes|boolean', ]; } } diff --git a/app/Models/Server.php b/app/Models/Server.php index d7cc649c09..5f2d6a49ef 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -115,6 +115,7 @@ class Server extends Model public const STATUS_INSTALLING = 'installing'; public const STATUS_INSTALL_FAILED = 'install_failed'; + public const STATUS_REINSTALL_FAILED = 'reinstall_failed'; public const STATUS_SUSPENDED = 'suspended'; public const STATUS_RESTORING_BACKUP = 'restoring_backup'; From df9a7f71f99a7afcd93de02b6b96fdf2ecc7dabf Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Mon, 21 Nov 2022 12:58:55 -0800 Subject: [PATCH 279/458] Support canceling file uploads (#4441) Closes #4440 --- package.json | 1 + .../components/elements/dialog/Dialog.tsx | 2 +- .../server/files/FileManagerStatus.tsx | 41 +++++++++++------ .../components/server/files/UploadButton.tsx | 45 +++++++++---------- resources/scripts/state/server/files.ts | 35 ++++++++++----- yarn.lock | 18 ++++++++ 6 files changed, 92 insertions(+), 50 deletions(-) diff --git a/package.json b/package.json index 562c22f7e3..e716485cb0 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "@headlessui/react": "^1.6.4", "@heroicons/react": "^1.0.6", "@hot-loader/react-dom": "^16.14.0", + "@preact/signals-react": "^1.2.1", "@tailwindcss/forms": "^0.5.2", "@tailwindcss/line-clamp": "^0.4.0", "axios": "^0.27.2", diff --git a/resources/scripts/components/elements/dialog/Dialog.tsx b/resources/scripts/components/elements/dialog/Dialog.tsx index b280f09448..bb35fe8fb8 100644 --- a/resources/scripts/components/elements/dialog/Dialog.tsx +++ b/resources/scripts/components/elements/dialog/Dialog.tsx @@ -90,7 +90,7 @@ export default ({ >
{iconPosition === 'container' && icon} -
+
{iconPosition !== 'container' && icon}
diff --git a/resources/scripts/components/server/files/FileManagerStatus.tsx b/resources/scripts/components/server/files/FileManagerStatus.tsx index 2e4144d0fd..620e067c17 100644 --- a/resources/scripts/components/server/files/FileManagerStatus.tsx +++ b/resources/scripts/components/server/files/FileManagerStatus.tsx @@ -1,11 +1,12 @@ -import React, { useContext, useEffect, useState } from 'react'; +import React, { useContext, useEffect } from 'react'; import { ServerContext } from '@/state/server'; -import { CloudUploadIcon } from '@heroicons/react/solid'; +import { CloudUploadIcon, XIcon } from '@heroicons/react/solid'; import asDialog from '@/hoc/asDialog'; import { Dialog, DialogWrapperContext } from '@/components/elements/dialog'; import { Button } from '@/components/elements/button/index'; import Tooltip from '@/components/elements/tooltip/Tooltip'; import Code from '@/components/elements/Code'; +import { useSignal } from '@preact/signals-react'; const svgProps = { cx: 16, @@ -31,23 +32,34 @@ const Spinner = ({ progress, className }: { progress: number; className?: string const FileUploadList = () => { const { close } = useContext(DialogWrapperContext); + const removeFileUpload = ServerContext.useStoreActions((actions) => actions.files.removeFileUpload); + const clearFileUploads = ServerContext.useStoreActions((actions) => actions.files.clearFileUploads); const uploads = ServerContext.useStoreState((state) => - state.files.uploads.sort((a, b) => a.name.localeCompare(b.name)) + Object.entries(state.files.uploads).sort(([a], [b]) => a.localeCompare(b)) ); return (
- {uploads.map((file) => ( -
+ {uploads.map(([name, file]) => ( +
- {file.name} + {name} +
))} + clearFileUploads()}> + Cancel Uploads + Close
@@ -60,17 +72,17 @@ const FileUploadListDialog = asDialog({ })(FileUploadList); export default () => { - const [open, setOpen] = useState(false); + const open = useSignal(false); - const count = ServerContext.useStoreState((state) => state.files.uploads.length); + const count = ServerContext.useStoreState((state) => Object.keys(state.files.uploads).length); const progress = ServerContext.useStoreState((state) => ({ - uploaded: state.files.uploads.reduce((count, file) => count + file.loaded, 0), - total: state.files.uploads.reduce((count, file) => count + file.total, 0), + uploaded: Object.values(state.files.uploads).reduce((count, file) => count + file.loaded, 0), + total: Object.values(state.files.uploads).reduce((count, file) => count + file.total, 0), })); useEffect(() => { if (count === 0) { - setOpen(false); + open.value = false; } }, [count]); @@ -78,13 +90,16 @@ export default () => { <> {count > 0 && ( - )} - + (open.value = false)} /> ); }; diff --git a/resources/scripts/components/server/files/UploadButton.tsx b/resources/scripts/components/server/files/UploadButton.tsx index 25277456d1..c5cac0e29b 100644 --- a/resources/scripts/components/server/files/UploadButton.tsx +++ b/resources/scripts/components/server/files/UploadButton.tsx @@ -2,7 +2,7 @@ import axios from 'axios'; import getFileUploadUrl from '@/api/server/files/getFileUploadUrl'; import tw from 'twin.macro'; import { Button } from '@/components/elements/button/index'; -import React, { useEffect, useRef, useState } from 'react'; +import React, { useEffect, useRef } from 'react'; import { ModalMask } from '@/components/elements/Modal'; import Fade from '@/components/elements/Fade'; import useEventListener from '@/plugins/useEventListener'; @@ -12,6 +12,7 @@ import { ServerContext } from '@/state/server'; import { WithClassname } from '@/components/types'; import Portal from '@/components/elements/Portal'; import { CloudUploadIcon } from '@heroicons/react/outline'; +import { useSignal } from '@preact/signals-react'; function isFileOrDirectory(event: DragEvent): boolean { if (!event.dataTransfer?.types) { @@ -23,14 +24,16 @@ function isFileOrDirectory(event: DragEvent): boolean { export default ({ className }: WithClassname) => { const fileUploadInput = useRef(null); - const [timeouts, setTimeouts] = useState([]); - const [visible, setVisible] = useState(false); + + const visible = useSignal(false); + const timeouts = useSignal([]); + const { mutate } = useFileManagerSwr(); const { addError, clearAndAddHttpError } = useFlashKey('files'); const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid); const directory = ServerContext.useStoreState((state) => state.files.directory); - const { clearFileUploads, appendFileUpload, removeFileUpload } = ServerContext.useStoreActions( + const { clearFileUploads, removeFileUpload, pushFileUpload, setUploadProgress } = ServerContext.useStoreActions( (actions) => actions.files ); @@ -40,27 +43,24 @@ export default ({ className }: WithClassname) => { e.preventDefault(); e.stopPropagation(); if (isFileOrDirectory(e)) { - return setVisible(true); + visible.value = true; } }, { capture: true } ); - useEventListener('dragexit', () => setVisible(false), { capture: true }); + useEventListener('dragexit', () => (visible.value = false), { capture: true }); - useEventListener('keydown', () => { - visible && setVisible(false); - }); + useEventListener('keydown', () => (visible.value = false)); useEffect(() => { - return () => timeouts.forEach(clearTimeout); + return () => timeouts.value.forEach(clearTimeout); }, []); const onUploadProgress = (data: ProgressEvent, name: string) => { - appendFileUpload({ name, loaded: data.loaded, total: data.total }); + setUploadProgress({ name, loaded: data.loaded }); if (data.loaded >= data.total) { - const timeout = setTimeout(() => removeFileUpload(name), 500); - setTimeouts((t) => [...t, timeout]); + timeouts.value.push(setTimeout(() => removeFileUpload(name), 500)); } }; @@ -71,23 +71,20 @@ export default ({ className }: WithClassname) => { return addError('Folder uploads are not supported at this time.', 'Error'); } - if (!list.length) { - return; - } - const uploads = list.map((file) => { - appendFileUpload({ name: file.name, loaded: 0, total: file.size }); + const controller = new AbortController(); + pushFileUpload({ name: file.name, data: { abort: controller, loaded: 0, total: file.size } }); + return () => getFileUploadUrl(uuid).then((url) => axios.post( url, { files: file }, { + signal: controller.signal, headers: { 'Content-Type': 'multipart/form-data' }, params: { directory }, - onUploadProgress: (data) => { - onUploadProgress(data, file.name); - }, + onUploadProgress: (data) => onUploadProgress(data, file.name), } ) ); @@ -104,15 +101,15 @@ export default ({ className }: WithClassname) => { return ( <> - + setVisible(false)} + onClick={() => (visible.value = false)} onDragOver={(e) => e.preventDefault()} onDrop={(e) => { e.preventDefault(); e.stopPropagation(); - setVisible(false); + visible.value = false; if (!e.dataTransfer?.files.length) return; onFileSubmission(e.dataTransfer.files); diff --git a/resources/scripts/state/server/files.ts b/resources/scripts/state/server/files.ts index 4a4b7fb93a..7e4786ae4c 100644 --- a/resources/scripts/state/server/files.ts +++ b/resources/scripts/state/server/files.ts @@ -1,31 +1,32 @@ import { action, Action } from 'easy-peasy'; import { cleanDirectoryPath } from '@/helpers'; -export interface FileUpload { - name: string; +export interface FileUploadData { loaded: number; + readonly abort: AbortController; readonly total: number; } export interface ServerFileStore { directory: string; selectedFiles: string[]; - uploads: FileUpload[]; + uploads: Record; setDirectory: Action; setSelectedFiles: Action; appendSelectedFile: Action; removeSelectedFile: Action; + pushFileUpload: Action; + setUploadProgress: Action; clearFileUploads: Action; - appendFileUpload: Action; removeFileUpload: Action; } const files: ServerFileStore = { directory: '/', selectedFiles: [], - uploads: [], + uploads: {}, setDirectory: action((state, payload) => { state.directory = cleanDirectoryPath(payload); @@ -44,19 +45,29 @@ const files: ServerFileStore = { }), clearFileUploads: action((state) => { - state.uploads = []; + Object.values(state.uploads).forEach((upload) => upload.abort.abort()); + + state.uploads = {}; + }), + + pushFileUpload: action((state, payload) => { + state.uploads[payload.name] = payload.data; }), - appendFileUpload: action((state, payload) => { - if (!state.uploads.some(({ name }) => name === payload.name)) { - state.uploads = [...state.uploads, payload]; - } else { - state.uploads = state.uploads.map((file) => (file.name === payload.name ? payload : file)); + setUploadProgress: action((state, { name, loaded }) => { + if (state.uploads[name]) { + state.uploads[name].loaded = loaded; } }), removeFileUpload: action((state, payload) => { - state.uploads = state.uploads.filter(({ name }) => name !== payload); + if (state.uploads[payload]) { + // Abort the request if it is still in flight. If it already completed this is + // a no-op. + state.uploads[payload].abort.abort(); + + delete state.uploads[payload]; + } }), }; diff --git a/yarn.lock b/yarn.lock index e29b8781d7..3142ac7558 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1493,6 +1493,19 @@ mkdirp "^1.0.4" rimraf "^3.0.2" +"@preact/signals-core@^1.2.2": + version "1.2.2" + resolved "https://registry.yarnpkg.com/@preact/signals-core/-/signals-core-1.2.2.tgz#279dcc5ab249de2f2e8f6e6779b1958256ba843e" + integrity sha512-z3/bCj7rRA21RJb4FeJ4guCrD1CQbaURHkCTunUWQpxUMAFOPXCD8tSFqERyGrrcSb4T3Hrmdc1OAl0LXBHwiw== + +"@preact/signals-react@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@preact/signals-react/-/signals-react-1.2.1.tgz#6d5d305ebdb38c879043acebc65c0d9351e663c1" + integrity sha512-73J8sL1Eru7Ot4yBYOCPj1izEZjzCEXlembRgk6C7PkwsqoAVbCxMlDOFfCLoPFuJ6qeGatrJzRkcycXppMqVQ== + dependencies: + "@preact/signals-core" "^1.2.2" + use-sync-external-store "^1.2.0" + "@sinclair/typebox@^0.23.3": version "0.23.5" resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.23.5.tgz#93f7b9f4e3285a7a9ade7557d9a8d36809cbc47d" @@ -9121,6 +9134,11 @@ use-memo-one@^1.1.1: resolved "https://registry.yarnpkg.com/use-memo-one/-/use-memo-one-1.1.1.tgz#39e6f08fe27e422a7d7b234b5f9056af313bd22c" integrity sha512-oFfsyun+bP7RX8X2AskHNTxu+R3QdE/RC5IefMbqptmACAA/gfol1KDD5KRzPsGMa62sWxGZw+Ui43u6x4ddoQ== +use-sync-external-store@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a" + integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA== + use@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/use/-/use-3.1.0.tgz#14716bf03fdfefd03040aef58d8b4b85f3a7c544" From ee033d6d08b68b710c5401741de0238f75435c0b Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Tue, 22 Nov 2022 13:39:43 -0700 Subject: [PATCH 280/458] Telemetry (#4564) --- app/Console/Commands/TelemetryCommand.php | 34 ++++ app/Console/Kernel.php | 32 ++++ .../Wings/DaemonConfigurationRepository.php | 4 +- .../Telemetry/TelemetryCollectionService.php | 175 ++++++++++++++++++ config/pterodactyl.php | 12 ++ 5 files changed, 255 insertions(+), 2 deletions(-) create mode 100644 app/Console/Commands/TelemetryCommand.php create mode 100644 app/Services/Telemetry/TelemetryCollectionService.php diff --git a/app/Console/Commands/TelemetryCommand.php b/app/Console/Commands/TelemetryCommand.php new file mode 100644 index 0000000000..3e1b0c2f87 --- /dev/null +++ b/app/Console/Commands/TelemetryCommand.php @@ -0,0 +1,34 @@ +output->info('Collecting telemetry data, this may take a while...'); + + VarDumper::dump($this->telemetryCollectionService->collect()); + } +} diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 17b3d4de44..a00b17b6d6 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -2,10 +2,13 @@ namespace Pterodactyl\Console; +use Ramsey\Uuid\Uuid; use Pterodactyl\Models\ActivityLog; use Illuminate\Console\Scheduling\Schedule; use Illuminate\Database\Console\PruneCommand; +use Pterodactyl\Repositories\Eloquent\SettingsRepository; use Illuminate\Foundation\Console\Kernel as ConsoleKernel; +use Pterodactyl\Services\Telemetry\TelemetryCollectionService; use Pterodactyl\Console\Commands\Schedule\ProcessRunnableCommand; use Pterodactyl\Console\Commands\Maintenance\PruneOrphanedBackupsCommand; use Pterodactyl\Console\Commands\Maintenance\CleanServiceBackupFilesCommand; @@ -37,5 +40,34 @@ protected function schedule(Schedule $schedule) if (config('activity.prune_days')) { $schedule->command(PruneCommand::class, ['--model' => [ActivityLog::class]])->daily(); } + + if (config('pterodactyl.telemetry.enabled')) { + $this->registerTelemetry($schedule); + } + } + + /** + * I wonder what this does. + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Illuminate\Contracts\Container\BindingResolutionException + */ + private function registerTelemetry(Schedule $schedule): void + { + $settingsRepository = app()->make(SettingsRepository::class); + + $uuid = $settingsRepository->get('app:uuid'); + if (is_null($uuid)) { + $uuid = Uuid::uuid4()->toString(); + $settingsRepository->set('app:uuid', $uuid); + } + + // Calculate a fixed time to run the data push at, this will be the same time every day. + $time = hexdec(str_replace('-', '', substr($uuid, 27))) % 1440; + $hour = floor($time / 60); + $minute = $time % 60; + + // Run the telemetry collector. + $schedule->call(app()->make(TelemetryCollectionService::class))->description('Collect Telemetry')->dailyAt("$hour:$minute"); } } diff --git a/app/Repositories/Wings/DaemonConfigurationRepository.php b/app/Repositories/Wings/DaemonConfigurationRepository.php index d24fb7e501..5580f9a8be 100644 --- a/app/Repositories/Wings/DaemonConfigurationRepository.php +++ b/app/Repositories/Wings/DaemonConfigurationRepository.php @@ -14,10 +14,10 @@ class DaemonConfigurationRepository extends DaemonRepository * * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException */ - public function getSystemInformation(): array + public function getSystemInformation(?int $version = null): array { try { - $response = $this->getHttpClient()->get('/api/system'); + $response = $this->getHttpClient()->get('/api/system' . (!is_null($version) ? '?v=' . $version : '')); } catch (TransferException $exception) { throw new DaemonConnectionException($exception); } diff --git a/app/Services/Telemetry/TelemetryCollectionService.php b/app/Services/Telemetry/TelemetryCollectionService.php new file mode 100644 index 0000000000..c23f41dc05 --- /dev/null +++ b/app/Services/Telemetry/TelemetryCollectionService.php @@ -0,0 +1,175 @@ +collect(); + } catch (Exception) { + return; + } + + Http::post('https://telemetry.pterodactyl.io', $data); + } + + /** + * Collects telemetry data and returns it as an array. + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + */ + public function collect(): array + { + $uuid = $this->settingsRepository->get('app:uuid'); + if (is_null($uuid)) { + $uuid = Uuid::uuid4()->toString(); + $this->settingsRepository->set('app:uuid', $uuid); + } + + $nodes = Node::all()->map(function ($node) { + try { + $info = $this->daemonConfigurationRepository->setNode($node)->getSystemInformation(2); + } catch (Exception) { + return null; + } + + return [ + 'id' => $node->uuid, + 'version' => Arr::get($info, 'version', ''), + + 'docker' => [ + 'version' => Arr::get($info, 'docker.version', ''), + + 'cgroups' => [ + 'driver' => Arr::get($info, 'docker.cgroups.driver', ''), + 'version' => Arr::get($info, 'docker.cgroups.version', ''), + ], + + 'containers' => [ + 'total' => Arr::get($info, 'docker.containers.total', -1), + 'running' => Arr::get($info, 'docker.containers.running', -1), + 'paused' => Arr::get($info, 'docker.containers.paused', -1), + 'stopped' => Arr::get($info, 'docker.containers.stopped', -1), + ], + + 'storage' => [ + 'driver' => Arr::get($info, 'docker.storage.driver', ''), + 'filesystem' => Arr::get($info, 'docker.storage.filesystem', ''), + ], + + 'runc' => [ + 'version' => Arr::get($info, 'docker.runc.version', ''), + ], + ], + + 'system' => [ + 'architecture' => Arr::get($info, 'system.architecture', ''), + 'cpuThreads' => Arr::get($info, 'system.cpu_threads', ''), + 'memoryBytes' => Arr::get($info, 'system.memory_bytes', ''), + 'kernelVersion' => Arr::get($info, 'system.kernel_version', ''), + 'os' => Arr::get($info, 'system.os', ''), + 'osType' => Arr::get($info, 'system.os_type', ''), + ], + ]; + })->filter(fn ($node) => !is_null($node))->toArray(); + + return [ + 'id' => $uuid, + + 'panel' => [ + 'version' => config('app.version'), + 'phpVersion' => phpversion(), + + 'drivers' => [ + 'backup' => [ + 'type' => config('backups.default'), + ], + 'cache' => [ + 'type' => config('cache.default'), + ], + 'database' => [ + 'type' => config('database.default'), + 'version' => DB::getPdo()->getAttribute(PDO::ATTR_SERVER_VERSION), + ], + ], + ], + + 'resources' => [ + 'allocations' => [ + 'count' => Allocation::count(), + 'used' => Allocation::whereNotNull('server_id')->count(), + ], + + 'backups' => [ + 'count' => Backup::count(), + 'bytes' => Backup::sum('bytes'), + ], + + 'eggs' => [ + 'count' => Egg::count(), + 'ids' => Egg::pluck('uuid')->toArray(), + ], + + 'locations' => [ + 'count' => Location::count(), + ], + + 'mounts' => [ + 'count' => Mount::count(), + ], + + 'nests' => [ + 'count' => Nest::count(), + ], + + 'nodes' => [ + 'count' => Node::count(), + ], + + 'servers' => [ + 'count' => Server::count(), + 'suspended' => Server::where('status', Server::STATUS_SUSPENDED)->count(), + ], + + 'users' => [ + 'count' => User::count(), + 'admins' => User::where('root_admin', true)->count(), + ], + ], + + 'nodes' => $nodes, + ]; + } +} diff --git a/config/pterodactyl.php b/config/pterodactyl.php index 58c58bc9eb..e5ceb35256 100644 --- a/config/pterodactyl.php +++ b/config/pterodactyl.php @@ -177,4 +177,16 @@ // Should an email be sent to a server owner whenever their server is reinstalled? 'send_reinstall_notification' => env('PTERODACTYL_SEND_REINSTALL_NOTIFICATION', true), ], + + /* + |-------------------------------------------------------------------------- + | Telemetry Settings + |-------------------------------------------------------------------------- + | + | This section controls the telemetry sent by Pterodactyl. + */ + + 'telemetry' => [ + 'enabled' => env('PTERODACTYL_TELEMETRY_ENABLED', false), + ], ]; From 5b36313c579ed0fe5fa151c16ce0c6f2be32580f Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Tue, 22 Nov 2022 13:37:46 -0700 Subject: [PATCH 281/458] Update README.md --- README.md | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 79efc44e6d..9163c49627 100644 --- a/README.md +++ b/README.md @@ -27,17 +27,18 @@ Stop settling for less. Make game servers a first class citizen on your platform I would like to extend my sincere thanks to the following sponsors for helping fund Pterodactyl's developement. [Interested in becoming a sponsor?](https://github.com/sponsors/matthewpi) -| Company | About | -|-----------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| [**WISP**](https://wisp.gg) | Extra features. | -| [**Fragnet**](https://fragnet.net) | Providing low latency, high-end game hosting solutions to gamers, game studios and eSports platforms. | -| [**RocketNode**](https://rocketnode.com/) | Innovative game server hosting combined with a straightforward control panel, affordable prices, and Rocket-Fast support. | -| [**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. | -| [**BisectHosting**](https://www.bisecthosting.com/) | BisectHosting provides Minecraft, Valheim and other server hosting services with the highest reliability and lightning fast support since 2012. | -| [**MineStrator**](https://minestrator.com/) | Looking for the most highend French hosting company for your minecraft server? More than 24,000 members on our discord trust us. Give us a try! | -| [**Skynode**](https://www.skynode.pro/) | Skynode provides blazing fast game servers along with a top-notch user experience. Whatever our clients are looking for, we're able to provide it! | -| [**VibeGAMES**](https://vibegames.net/) | VibeGAMES is a game server provider that specializes in DDOS protection for the games we offer. We have multiple locations in the US, Brazil, France, Germany, Singapore, Australia and South Africa. | -| [**Pterodactyl Market**](https://pterodactylmarket.com/) | Pterodactyl Market is a one-and-stop shop for Pterodactyl. In our market, you can find Add-ons, Themes, Eggs, and more for Pterodactyl. | +| Company | About | +|-----------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| [**WISP**](https://wisp.gg) | Extra features. | +| [**Fragnet**](https://fragnet.net) | Providing low latency, high-end game hosting solutions to gamers, game studios and eSports platforms. | +| [**RocketNode**](https://rocketnode.com/) | Innovative game server hosting combined with a straightforward control panel, affordable prices, and Rocket-Fast support. | +| [**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. | +| [**BisectHosting**](https://www.bisecthosting.com/) | BisectHosting provides Minecraft, Valheim and other server hosting services with the highest reliability and lightning fast support since 2012. | +| [**MineStrator**](https://minestrator.com/) | Looking for the most highend French hosting company for your minecraft server? More than 24,000 members on our discord trust us. Give us a try! | +| [**Skynode**](https://www.skynode.pro/) | Skynode provides blazing fast game servers along with a top-notch user experience. Whatever our clients are looking for, we're able to provide it! | +| [**VibeGAMES**](https://vibegames.net/) | VibeGAMES is a game server provider that specializes in DDOS protection for the games we offer. We have multiple locations in the US, Brazil, France, Germany, Singapore, Australia and South Africa. | +| [**Pterodactyl Market**](https://pterodactylmarket.com/) | Pterodactyl Market is a one-and-stop shop for Pterodactyl. In our market, you can find Add-ons, Themes, Eggs, and more for Pterodactyl. | +| [**UltraServers**](https://ultraservers.com/) | Deploy premium games hosting with the click of a button. Manage and swap games with ease and let us take care of the rest. We currently support Minecraft, Rust, ARK, 7 Days to Die, Garys MOD, CS:GO, Satisfactory and others. | ### Supported Games From 1bb1b13f6d1247a051f249016c5b8fe985e64817 Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Tue, 22 Nov 2022 13:40:58 -0700 Subject: [PATCH 282/458] Update CHANGELOG.md --- CHANGELOG.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 371916bc96..6ad1ebca5b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,20 @@ 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.11.0-rc.2 +### Changed +* `MB` byte suffix are now `MiB` to more accurately reflect the actual value. +* Server reinstallation failures are tracked independently of the initial installation process. + +### Fixed +* Properly handle a missing `Content-Length` header in the response from the daemon. +* Ensure activity log properties are always returned as an object instead of an empty array. + +### Added +* Added the `server:settings.description` activity log event for when a server description is changed. +* Added the ability to cancel file uploads in the file manager for a server. +* Added a telemetry service to collect anonymous metrics from the panel, this feature is disabled by default and can be toggled using the `PTERODACTYL_TELEMETRY_ENABLED` environment variable. + ## v1.11.0-rc.1 ### Changed * Changed minimum PHP version is now 8.0 instead of `7.4`. From 21613fa60256b5668ce7d79de18cb1540d1c52f4 Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Fri, 25 Nov 2022 13:25:03 -0700 Subject: [PATCH 283/458] React 18 and Vite (#4510) --- .eslintrc.js | 11 +- .github/workflows/{build.yaml => ui.yaml} | 13 +- .gitignore | 5 + .prettierignore | 4 + .prettierrc.json | 22 + app/Http/ViewComposers/AssetComposer.php | 9 - app/Services/Helpers/AssetHashService.php | 117 - babel.config.js | 34 - docker-compose.development.yaml | 125 + flake.lock | 12 +- flake.nix | 112 +- jest.config.js | 28 - package.json | 253 +- postcss.config.js => postcss.config.cjs | 0 resources/scripts/TransitionRouter.tsx | 30 - resources/scripts/__mocks__/file.ts | 1 - resources/scripts/api/account/activity.ts | 14 +- resources/scripts/api/account/createApiKey.ts | 2 +- resources/scripts/api/account/ssh-keys.ts | 10 +- resources/scripts/api/auth/login.ts | 4 +- resources/scripts/api/auth/loginCheckpoint.ts | 4 +- .../scripts/api/auth/performPasswordReset.ts | 4 +- .../api/auth/requestPasswordResetEmail.ts | 2 +- resources/scripts/api/definitions/helpers.ts | 8 +- resources/scripts/api/getServers.ts | 2 +- resources/scripts/api/http.ts | 8 +- resources/scripts/api/interceptors.ts | 13 +- resources/scripts/api/server/activity.ts | 20 +- .../server/databases/createServerDatabase.ts | 4 +- .../server/databases/getServerDatabases.ts | 4 +- .../databases/rotateDatabasePassword.ts | 2 +- .../scripts/api/server/files/compressFiles.ts | 2 +- .../api/server/files/decompressFiles.ts | 2 +- .../api/server/files/getFileContents.ts | 2 +- resources/scripts/api/server/getServer.ts | 8 +- .../api/server/getServerResourceUsage.ts | 2 +- .../scripts/api/server/getWebsocketToken.ts | 2 +- .../schedules/createOrUpdateScheduleTask.ts | 2 +- .../api/server/users/createOrUpdateSubuser.ts | 2 +- .../api/server/users/getServerSubusers.ts | 2 +- .../scripts/api/swr/getServerAllocations.ts | 9 +- resources/scripts/api/swr/getServerBackups.ts | 10 +- resources/scripts/api/swr/getServerStartup.ts | 11 +- resources/scripts/api/transformers.ts | 2 +- .../scripts/assets/css/GlobalStylesheet.ts | 2 +- resources/scripts/components/App.tsx | 102 +- resources/scripts/components/Avatar.tsx | 3 +- .../scripts/components/FlashMessageRender.tsx | 10 +- resources/scripts/components/MessageBox.tsx | 5 +- .../scripts/components/NavigationBar.tsx | 38 +- .../auth/ForgotPasswordContainer.tsx | 37 +- .../auth/LoginCheckpointContainer.tsx | 44 +- .../components/auth/LoginContainer.tsx | 34 +- .../components/auth/LoginFormContainer.tsx | 5 +- .../auth/ResetPasswordContainer.tsx | 18 +- .../dashboard/AccountApiContainer.tsx | 12 +- .../dashboard/AccountOverviewContainer.tsx | 17 +- .../components/dashboard/ApiKeyModal.tsx | 2 +- .../dashboard/DashboardContainer.tsx | 10 +- .../components/dashboard/ServerRow.tsx | 19 +- .../activity/ActivityLogContainer.tsx | 10 +- .../forms/ConfigureTwoFactorForm.tsx | 2 +- .../dashboard/forms/CreateApiKeyForm.tsx | 6 +- .../dashboard/forms/DisableTOTPDialog.tsx | 9 +- .../dashboard/forms/RecoveryTokensDialog.tsx | 3 +- .../dashboard/forms/SetupTOTPDialog.tsx | 15 +- .../forms/UpdateEmailAddressForm.tsx | 12 +- .../dashboard/forms/UpdatePasswordForm.tsx | 16 +- .../dashboard/search/SearchContainer.tsx | 5 +- .../dashboard/search/SearchModal.tsx | 18 +- .../dashboard/ssh/AccountSSHContainer.tsx | 2 +- .../dashboard/ssh/CreateSSHKeyForm.tsx | 9 +- .../dashboard/ssh/DeleteSSHKeyButton.tsx | 6 +- .../elements/AuthenticatedRoute.tsx | 28 +- .../scripts/components/elements/Button.tsx | 36 +- resources/scripts/components/elements/Can.tsx | 21 +- .../scripts/components/elements/Checkbox.tsx | 3 +- .../scripts/components/elements/Code.tsx | 2 +- .../components/elements/CodemirrorEditor.tsx | 21 +- .../components/elements/ConfirmationModal.tsx | 21 +- .../components/elements/ContentBox.tsx | 2 +- .../components/elements/ContentContainer.tsx | 2 +- .../components/elements/CopyOnClick.tsx | 29 +- .../components/elements/DropdownMenu.tsx | 52 +- .../components/elements/ErrorBoundary.tsx | 20 +- .../scripts/components/elements/Fade.tsx | 47 - .../scripts/components/elements/Field.tsx | 5 +- .../elements/FormikFieldWrapper.tsx | 2 +- .../components/elements/FormikSwitch.tsx | 1 - .../components/elements/GreyRowBox.tsx | 4 +- .../scripts/components/elements/Icon.tsx | 2 +- .../scripts/components/elements/Input.tsx | 10 +- .../components/elements/InputError.tsx | 3 +- .../components/elements/InputSpinner.tsx | 34 +- .../scripts/components/elements/Label.tsx | 4 +- .../scripts/components/elements/Modal.tsx | 64 +- .../components/elements/PageContentBlock.tsx | 54 +- .../components/elements/Pagination.tsx | 10 +- .../components/elements/PermissionRoute.tsx | 46 +- .../scripts/components/elements/Portal.tsx | 3 +- .../components/elements/ProgressBar.tsx | 52 +- .../components/elements/ScreenBlock.tsx | 3 +- .../scripts/components/elements/Select.tsx | 4 +- .../elements/ServerContentBlock.tsx | 4 +- .../scripts/components/elements/Spinner.tsx | 18 +- .../components/elements/SpinnerOverlay.tsx | 20 +- .../components/elements/SubNavigation.tsx | 2 +- .../scripts/components/elements/Switch.tsx | 15 +- .../components/elements/TitledGreyBox.tsx | 3 +- .../scripts/components/elements/Translate.tsx | 12 +- .../elements/activity/ActivityLogEntry.tsx | 2 +- .../activity/ActivityLogMetaButton.tsx | 2 +- .../components/elements/alert/Alert.tsx | 4 +- .../components/elements/button/Button.tsx | 6 +- .../components/elements/button/types.ts | 6 +- .../elements/dialog/ConfirmationDialog.tsx | 2 +- .../components/elements/dialog/Dialog.tsx | 3 +- .../elements/dialog/DialogFooter.tsx | 5 +- .../components/elements/dialog/DialogIcon.tsx | 4 +- .../components/elements/dialog/context.ts | 6 +- .../components/elements/dialog/types.d.ts | 2 +- .../components/elements/dropdown/Dropdown.tsx | 7 +- .../elements/dropdown/DropdownButton.tsx | 2 +- .../elements/dropdown/DropdownItem.tsx | 7 +- .../components/elements/editor/Editor.tsx | 206 + .../components/elements/editor/index.ts | 1 + .../components/elements/editor/theme.ts | 148 + .../components/elements/inputs/Checkbox.tsx | 3 +- .../components/elements/inputs/InputField.tsx | 5 +- .../components/elements/inputs/index.ts | 2 +- .../elements/table/PaginationFooter.tsx | 5 +- .../components/elements/tooltip/Tooltip.tsx | 5 +- .../elements/transitions/FadeTransition.tsx | 14 +- resources/scripts/components/history.ts | 3 - .../server/ConflictStateRenderer.tsx | 13 +- .../components/server/InstallListener.tsx | 12 +- .../server/ServerActivityLogContainer.tsx | 10 +- .../components/server/TransferListener.tsx | 12 +- .../components/server/UptimeDuration.tsx | 2 - .../components/server/WebsocketHandler.tsx | 49 +- .../server/backups/BackupContainer.tsx | 4 +- .../server/backups/BackupContextMenu.tsx | 74 +- .../components/server/backups/BackupRow.tsx | 15 +- .../server/backups/CreateBackupButton.tsx | 14 +- .../components/server/console/ChartBlock.tsx | 2 +- .../components/server/console/Console.tsx | 69 +- .../server/console/PowerButtons.tsx | 9 +- .../server/console/ServerConsoleContainer.tsx | 29 +- .../server/console/ServerDetailsBlock.tsx | 50 +- .../components/server/console/StatBlock.tsx | 20 +- .../components/server/console/StatGraphs.tsx | 6 +- .../components/server/console/chart.ts | 28 +- .../server/databases/CreateDatabaseButton.tsx | 12 +- .../server/databases/DatabaseRow.tsx | 12 +- .../server/databases/DatabasesContainer.tsx | 20 +- .../server/databases/RotatePasswordButton.tsx | 8 +- .../components/server/features/Features.tsx | 14 +- .../server/features/GSLTokenModalFeature.tsx | 12 +- .../features/JavaVersionModalFeature.tsx | 22 +- .../server/features/PIDLimitModalFeature.tsx | 10 +- .../server/features/SteamDiskSpaceFeature.tsx | 10 +- .../server/features/eula/EulaModalFeature.tsx | 12 +- .../server/files/ChmodFileModal.tsx | 25 +- .../server/files/FileDropdownMenu.tsx | 31 +- .../server/files/FileEditContainer.tsx | 68 +- .../server/files/FileManagerBreadcrumbs.tsx | 18 +- .../server/files/FileManagerContainer.tsx | 34 +- .../server/files/FileManagerStatus.tsx | 23 +- .../components/server/files/FileNameModal.tsx | 5 +- .../components/server/files/FileObjectRow.tsx | 95 +- .../server/files/MassActionsBar.tsx | 52 +- .../server/files/NewDirectoryButton.tsx | 12 +- .../server/files/RenameFileModal.tsx | 19 +- .../server/files/SelectFileCheckbox.tsx | 10 +- .../components/server/files/UploadButton.tsx | 57 +- .../server/network/AllocationRow.tsx | 16 +- .../server/network/DeleteAllocationButton.tsx | 12 +- .../server/network/NetworkContainer.tsx | 20 +- .../server/schedules/DeleteScheduleButton.tsx | 6 +- .../server/schedules/EditScheduleModal.tsx | 12 +- .../server/schedules/NewTaskButton.tsx | 2 +- .../server/schedules/RunScheduleButton.tsx | 8 +- .../schedules/ScheduleCheatsheetCards.tsx | 1 - .../server/schedules/ScheduleContainer.tsx | 35 +- .../server/schedules/ScheduleCronRow.tsx | 1 - .../schedules/ScheduleEditContainer.tsx | 34 +- .../server/schedules/ScheduleRow.tsx | 1 - .../server/schedules/ScheduleTaskRow.tsx | 12 +- .../server/schedules/TaskDetailsModal.tsx | 18 +- .../server/settings/ReinstallServerBox.tsx | 6 +- .../server/settings/RenameServerBox.tsx | 9 +- .../server/settings/SettingsContainer.tsx | 11 +- .../server/startup/StartupContainer.tsx | 23 +- .../components/server/startup/VariableBox.tsx | 32 +- .../server/users/AddSubuserButton.tsx | 2 +- .../server/users/EditSubuserModal.tsx | 34 +- .../components/server/users/PermissionRow.tsx | 11 +- .../server/users/PermissionTitleBox.tsx | 30 +- .../server/users/RemoveSubuserButton.tsx | 8 +- .../components/server/users/UserRow.tsx | 6 +- .../server/users/UsersContainer.tsx | 16 +- resources/scripts/context/ModalContext.ts | 6 +- resources/scripts/helpers.ts | 2 +- .../scripts/hoc/RequireServerPermission.tsx | 9 +- resources/scripts/hoc/asDialog.tsx | 9 +- resources/scripts/hoc/asModal.tsx | 21 +- resources/scripts/i18n.ts | 2 +- resources/scripts/index.tsx | 15 +- resources/scripts/lib/formatters.spec.ts | 42 +- resources/scripts/lib/helpers.spec.ts | 38 +- resources/scripts/lib/helpers.ts | 2 +- resources/scripts/lib/objects.spec.ts | 24 +- resources/scripts/lib/strings.spec.ts | 16 +- resources/scripts/macros.d.ts | 4 +- resources/scripts/plugins/Websocket.ts | 6 +- resources/scripts/plugins/useEventListener.ts | 2 +- .../scripts/plugins/useFileManagerSwr.ts | 6 +- resources/scripts/plugins/useFlash.ts | 2 +- resources/scripts/plugins/useLocationHash.ts | 6 +- resources/scripts/plugins/usePermissions.ts | 8 +- .../scripts/plugins/usePersistedState.ts | 2 +- resources/scripts/plugins/useSWRKey.ts | 6 +- .../scripts/plugins/useWebsocketEvent.ts | 2 +- .../scripts/routers/AuthenticationRouter.tsx | 28 +- resources/scripts/routers/DashboardRouter.tsx | 51 +- resources/scripts/routers/ServerRouter.tsx | 126 +- resources/scripts/routers/routes.ts | 82 +- resources/scripts/setup-tests.ts | 1 - resources/scripts/state/flashes.ts | 2 +- resources/scripts/state/permissions.ts | 2 +- resources/scripts/state/progress.ts | 4 +- resources/scripts/state/server/databases.ts | 6 +- resources/scripts/state/server/files.ts | 39 +- resources/scripts/state/server/index.ts | 87 +- resources/scripts/state/server/schedules.ts | 6 +- resources/scripts/state/server/subusers.ts | 4 +- resources/scripts/theme.ts | 3 +- resources/views/templates/wrapper.blade.php | 8 +- shell.nix | 19 +- tailwind.config.js | 6 +- tsconfig.json | 96 +- vite.config.ts | 62 + webpack.config.js | 156 - yarn.lock | 8560 ++++------------- 244 files changed, 4535 insertions(+), 8921 deletions(-) rename .github/workflows/{build.yaml => ui.yaml} (78%) create mode 100644 .prettierignore create mode 100644 .prettierrc.json delete mode 100644 app/Services/Helpers/AssetHashService.php delete mode 100644 babel.config.js create mode 100644 docker-compose.development.yaml delete mode 100644 jest.config.js rename postcss.config.js => postcss.config.cjs (100%) delete mode 100644 resources/scripts/TransitionRouter.tsx delete mode 100644 resources/scripts/__mocks__/file.ts delete mode 100644 resources/scripts/components/elements/Fade.tsx create mode 100644 resources/scripts/components/elements/editor/Editor.tsx create mode 100644 resources/scripts/components/elements/editor/index.ts create mode 100644 resources/scripts/components/elements/editor/theme.ts delete mode 100644 resources/scripts/components/history.ts delete mode 100644 resources/scripts/setup-tests.ts create mode 100644 vite.config.ts delete mode 100644 webpack.config.js diff --git a/.eslintrc.js b/.eslintrc.js index 77547d8799..21dabafcd3 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,9 +1,3 @@ -const prettier = { - singleQuote: true, - jsxSingleQuote: true, - printWidth: 120, -}; - /** @type {import('eslint').Linter.Config} */ module.exports = { parser: '@typescript-eslint/parser', @@ -39,15 +33,16 @@ module.exports = { // 'standard', 'eslint:recommended', 'plugin:react/recommended', + 'plugin:react/jsx-runtime', 'plugin:@typescript-eslint/recommended', - 'plugin:jest-dom/recommended', ], rules: { eqeqeq: 'error', - 'prettier/prettier': ['error', prettier], + 'prettier/prettier': ['error', {}, {usePrettierrc: true}], // TypeScript can infer this significantly better than eslint ever can. 'react/prop-types': 0, 'react/display-name': 0, + 'react/no-unknown-property': ['error', {ignore: ['css']}], '@typescript-eslint/no-explicit-any': 0, '@typescript-eslint/no-non-null-assertion': 0, // This setup is required to avoid a spam of errors when running eslint about React being diff --git a/.github/workflows/build.yaml b/.github/workflows/ui.yaml similarity index 78% rename from .github/workflows/build.yaml rename to .github/workflows/ui.yaml index 4aca84ce80..e743de4667 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/ui.yaml @@ -1,4 +1,4 @@ -name: Build +name: UI on: push: @@ -11,13 +11,13 @@ on: - "1.0-develop" jobs: - ui: - name: UI + build-and-test: + name: Build and Test runs-on: ubuntu-20.04 strategy: fail-fast: false matrix: - node-version: [16] + node-version: [16, 18] steps: - name: Code Checkout uses: actions/checkout@v3 @@ -32,4 +32,7 @@ jobs: run: yarn install --frozen-lockfile - name: Build - run: yarn build:production + run: yarn build + + - name: Tests + run: yarn test diff --git a/.gitignore b/.gitignore index 4b5d022467..2c9fda4ca6 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,8 @@ misc coverage.xml resources/lang/locales.js .phpunit.result.cache + +/public/build +/public/hot +result +docker-compose.yaml diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000000..e5eee8ad45 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,4 @@ +.github +public +node_modules +resources/views diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000000..296147a809 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,22 @@ +{ + "printWidth": 120, + "tabWidth": 4, + "useTabs": false, + "semi": true, + "singleQuote": true, + "jsxSingleQuote": false, + "trailingComma": "all", + "bracketSpacing": true, + "arrowParens": "avoid", + "endOfLine": "lf", + "overrides": [ + { + "files": ["**/*.md"], + "options": { + "tabWidth": 2, + "useTabs": false, + "singleQuote": false + } + } + ] +} diff --git a/app/Http/ViewComposers/AssetComposer.php b/app/Http/ViewComposers/AssetComposer.php index d42f8a80ad..2bbb39d5ce 100644 --- a/app/Http/ViewComposers/AssetComposer.php +++ b/app/Http/ViewComposers/AssetComposer.php @@ -3,23 +3,14 @@ namespace Pterodactyl\Http\ViewComposers; use Illuminate\View\View; -use Pterodactyl\Services\Helpers\AssetHashService; class AssetComposer { - /** - * AssetComposer constructor. - */ - public function __construct(private AssetHashService $assetHashService) - { - } - /** * Provide access to the asset service in the views. */ public function compose(View $view): void { - $view->with('asset', $this->assetHashService); $view->with('siteConfiguration', [ 'name' => config('app.name') ?? 'Pterodactyl', 'locale' => config('app.locale') ?? 'en', diff --git a/app/Services/Helpers/AssetHashService.php b/app/Services/Helpers/AssetHashService.php deleted file mode 100644 index 725a566692..0000000000 --- a/app/Services/Helpers/AssetHashService.php +++ /dev/null @@ -1,117 +0,0 @@ -filesystem = $filesystem->createLocalDriver(['root' => public_path()]); - } - - /** - * Modify a URL to append the asset hash. - */ - public function url(string $resource): string - { - $file = last(explode('/', $resource)); - $data = Arr::get($this->manifest(), $file) ?? $file; - - return str_replace($file, Arr::get($data, 'src') ?? $file, $resource); - } - - /** - * Return the data integrity hash for a resource. - */ - public function integrity(string $resource): string - { - $file = last(explode('/', $resource)); - $data = array_get($this->manifest(), $file, $file); - - return Arr::get($data, 'integrity') ?? ''; - } - - /** - * Return a built CSS import using the provided URL. - */ - public function css(string $resource): string - { - $attributes = [ - 'href' => $this->url($resource), - 'rel' => 'stylesheet preload', - 'as' => 'style', - 'crossorigin' => 'anonymous', - 'referrerpolicy' => 'no-referrer', - ]; - - if (config('pterodactyl.assets.use_hash')) { - $attributes['integrity'] = $this->integrity($resource); - } - - $output = ' $value) { - $output .= " $key=\"$value\""; - } - - return $output . '>'; - } - - /** - * Return a built JS import using the provided URL. - */ - public function js(string $resource): string - { - $attributes = [ - 'src' => $this->url($resource), - 'crossorigin' => 'anonymous', - ]; - - if (config('pterodactyl.assets.use_hash')) { - $attributes['integrity'] = $this->integrity($resource); - } - - $output = ' $value) { - $output .= " $key=\"$value\""; - } - - return $output . '>'; - } - - /** - * Get the asset manifest and store it in the cache for quicker lookups. - */ - protected function manifest(): array - { - if (static::$manifest === null) { - self::$manifest = json_decode( - $this->filesystem->get(self::MANIFEST_PATH), - true - ); - } - - $manifest = static::$manifest; - if ($manifest === null) { - throw new ManifestDoesNotExistException(); - } - - return $manifest; - } -} diff --git a/babel.config.js b/babel.config.js deleted file mode 100644 index 808bb186f9..0000000000 --- a/babel.config.js +++ /dev/null @@ -1,34 +0,0 @@ -module.exports = function (api) { - let targets = {}; - const plugins = [ - 'babel-plugin-macros', - 'styled-components', - 'react-hot-loader/babel', - '@babel/transform-runtime', - '@babel/transform-react-jsx', - '@babel/proposal-class-properties', - '@babel/proposal-object-rest-spread', - '@babel/proposal-optional-chaining', - '@babel/proposal-nullish-coalescing-operator', - '@babel/syntax-dynamic-import', - ]; - - if (api.env('test')) { - targets = { node: 'current' }; - plugins.push('@babel/transform-modules-commonjs'); - } - - return { - plugins, - presets: [ - '@babel/typescript', - ['@babel/env', { - modules: false, - useBuiltIns: 'entry', - corejs: 3, - targets, - }], - '@babel/react', - ] - }; -}; diff --git a/docker-compose.development.yaml b/docker-compose.development.yaml new file mode 100644 index 0000000000..35b4f15306 --- /dev/null +++ b/docker-compose.development.yaml @@ -0,0 +1,125 @@ +# For more information: https://laravel.com/docs/sail +version: '3' + +services: + caddy: + image: localhost/pterodactyl/development:panel + network_mode: host + command: + - caddy + - run + - --config + - /etc/caddy/Caddyfile + volumes: + - '.:/var/www/html' + depends_on: + - laravel + + laravel: + image: localhost/pterodactyl/development:panel + network_mode: host + command: + - 'php-fpm' + - '--nodaemonize' + - '-y' + - '/etc/php-fpm.conf' + volumes: + - '.:/var/www/html' + tmpfs: + - '/tmp' + depends_on: + - pgsql + - mariadb + - redis + - mailhog + + pgsql: + image: docker.io/library/postgres:14 + ports: + - '127.0.0.1:${FORWARD_DB_PORT:-5432}:5432' + environment: + PGPASSWORD: '${DB_PASSWORD:-secret}' + POSTGRES_DB: '${DB_DATABASE}' + POSTGRES_USER: '${DB_USERNAME}' + POSTGRES_PASSWORD: '${DB_PASSWORD:-secret}' + volumes: + - 'sail-pgsql:/var/lib/postgresql/data' + - './vendor/laravel/sail/database/pgsql/create-testing-database.sql:/docker-entrypoint-initdb.d/10-create-testing-database.sql' + networks: + - sail + healthcheck: + test: ["CMD", "pg_isready", "-q", "-d", "${DB_DATABASE}", "-U", "${DB_USERNAME}"] + retries: 3 + timeout: 5s + + mariadb: + image: docker.io/library/mariadb:10 + ports: + - '127.0.0.1:${FORWARD_DB_PORT:-3306}:3306' + environment: + MYSQL_DATABASE: '${DB_DATABASE}' + MYSQL_USER: '${DB_USERNAME}' + MYSQL_PASSWORD: '${DB_PASSWORD}' + MYSQL_ALLOW_EMPTY_PASSWORD: 'yes' + volumes: + - 'sail-mariadb:/var/lib/mysql' + - './vendor/laravel/sail/database/mysql/create-testing-database.sh:/docker-entrypoint-initdb.d/10-create-testing-database.sh' + networks: + - sail + healthcheck: + test: ["CMD", "mysqladmin", "ping", "-p${DB_PASSWORD}"] + retries: 3 + timeout: 5s + + redis: + image: docker.io/library/redis:7 + ports: + - '127.0.0.1:${FORWARD_REDIS_PORT:-6379}:6379' + volumes: + - 'sail-redis:/data' + networks: + - sail + healthcheck: + test: ["CMD", "redis-cli", "ping"] + retries: 3 + timeout: 5s + + minio: + image: docker.io/minio/minio:latest + ports: + - '127.0.0.1:${FORWARD_MINIO_PORT:-9001}:9000' + - '127.0.0.1:${FORWARD_MINIO_CONSOLE_PORT:-8900}:8900' + environment: + MINIO_ROOT_USER: 'sail' + MINIO_ROOT_PASSWORD: 'password' + volumes: + - 'sail-minio:/data/minio' + networks: + - sail + command: minio server /data/minio --console-address ":8900" + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"] + retries: 3 + timeout: 5s + + mailhog: + image: docker.io/mailhog/mailhog:latest + ports: + - '127.0.0.1:${FORWARD_MAILHOG_PORT:-1025}:1025' + - '127.0.0.1:${FORWARD_MAILHOG_DASHBOARD_PORT:-8025}:8025' + networks: + - sail + +networks: + sail: + driver: bridge + +volumes: + sail-pgsql: + driver: local + sail-mariadb: + driver: local + sail-redis: + driver: local + sail-minio: + driver: local diff --git a/flake.lock b/flake.lock index 4a64ac9647..ec3c2152a6 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,11 @@ "nodes": { "flake-utils": { "locked": { - "lastModified": 1659877975, - "narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=", + "lastModified": 1667395993, + "narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=", "owner": "numtide", "repo": "flake-utils", - "rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0", + "rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f", "type": "github" }, "original": { @@ -17,11 +17,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1666539104, - "narHash": "sha256-jeuC+d375wHHxMOFLgu7etseCQVJuPNKoEc9X9CsErg=", + "lastModified": 1669140675, + "narHash": "sha256-npzfyfLECsJWgzK/M4gWhykP2DNAJTYjgY2BWkz/oEQ=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "0e6df35f39651504249a05191f9a78d251707e22", + "rev": "2788904d26dda6cfa1921c5abb7a2466ffe3cb8c", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 050cc2415c..f05682ef10 100644 --- a/flake.nix +++ b/flake.nix @@ -15,8 +15,118 @@ flake-utils.lib.eachDefaultSystem ( system: let pkgs = import nixpkgs {inherit system;}; + + php81WithExtensions = with pkgs; (php81.buildEnv { + extensions = { + enabled, + all, + }: + enabled + ++ (with all; [ + redis + xdebug + ]); + extraConfig = '' + xdebug.mode=debug + ''; + }); + + caCertificates = pkgs.runCommand "ca-certificates" {} '' + mkdir -p $out/etc/ssl/certs $out/etc/pki/tls/certs + ln -s ${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt $out/etc/ssl/certs/ca-bundle.crt + ln -s ${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt $out/etc/ssl/certs/ca-certificates.crt + ln -s ${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt $out/etc/pki/tls/certs/ca-bundle.crt + ''; + + caddyfile = pkgs.writeText "Caddyfile" '' + :80 { + root * /var/www/html/public/ + file_server + + header { + -Server + -X-Powered-By + Referrer-Policy "same-origin" + X-Frame-Options "deny" + X-XSS-Protection "1; mode=block" + X-Content-Type-Options "nosniff" + } + + encode gzip zstd + + php_fastcgi localhost:9000 + + @startsWithDot { + path \/\. + not path .well-known + } + rewrite @startsWithDot /index.php{uri} + + @phpRewrite { + not file favicon.ico + } + try_files @phpRewrite {path} {path}/ /index.php?{query} + } + ''; + + phpfpmConf = pkgs.writeText "php-fpm.conf" '' + [global] + error_log = /dev/stderr + daemonize = no + + [www] + user = nobody + group = nobody + + listen = 0.0.0.0:9000 + + pm = dynamic + pm.start_servers = 4 + pm.min_spare_servers = 4 + pm.max_spare_servers = 16 + pm.max_children = 64 + pm.max_requests = 256 + + clear_env = no + catch_workers_output = yes + + decorate_workers_output = no + ''; + + configs = pkgs.runCommand "configs" {} '' + mkdir -p $out/etc/caddy + ln -s ${caddyfile} $out/etc/caddy/Caddyfile + ln -s ${phpfpmConf} $out/etc/php-fpm.conf + ''; in { - devShell = import ./shell.nix {inherit pkgs;}; + devShell = import ./shell.nix {inherit pkgs php81WithExtensions;}; + + packages = { + development = pkgs.dockerTools.buildImage { + name = "pterodactyl/development"; + tag = "panel"; + + copyToRoot = pkgs.buildEnv { + name = "image-root"; + paths = with pkgs; [ + dockerTools.fakeNss + caCertificates + caddy + configs + coreutils + mysql80 + nodejs-18_x + nodePackages.npm + nodePackages.pnpm + nodePackages.yarn + php81WithExtensions + (php81Packages.composer.override {php = php81WithExtensions;}) + postgresql_14 + ]; + pathsToLink = ["/bin" "/etc"]; + }; + }; + }; } ); } diff --git a/jest.config.js b/jest.config.js deleted file mode 100644 index abb02f3860..0000000000 --- a/jest.config.js +++ /dev/null @@ -1,28 +0,0 @@ -const { pathsToModuleNameMapper } = require('ts-jest'); -const { compilerOptions } = require('./tsconfig'); - -/** @type {import('ts-jest').InitialOptionsTsJest} */ -module.exports = { - preset: 'ts-jest', - globals: { - 'ts-jest': { - isolatedModules: true, - }, - }, - moduleFileExtensions: ['js', 'ts', 'tsx', 'd.ts', 'json', 'node'], - moduleNameMapper: { - '\\.(jpe?g|png|gif|svg)$': '/resources/scripts/__mocks__/file.ts', - '\\.(s?css|less)$': 'identity-obj-proxy', - ...pathsToModuleNameMapper(compilerOptions.paths, { - prefix: '/', - }), - }, - setupFilesAfterEnv: [ - '/resources/scripts/setup-tests.ts', - ], - transform: { - '.*\\.[t|j]sx$': 'babel-jest', - '.*\\.ts$': 'ts-jest', - }, - testPathIgnorePatterns: ['/node_modules/'], -}; diff --git a/package.json b/package.json index e716485cb0..ef5fb95d61 100644 --- a/package.json +++ b/package.json @@ -1,142 +1,128 @@ { - "name": "pterodactyl-panel", + "name": "@pterodactyl/panel", + "license": "MIT", + "private": true, "engines": { - "node": ">=14" + "node": ">=16.0" + }, + "scripts": { + "build": "vite build", + "clean": "rimraf public/build", + "coverage": "vitest run --coverage", + "dev": "vite", + "lint": "eslint ./resources/scripts/**/*.{ts,tsx} --ext .ts,.tsx", + "test": "vitest run", + "test:ui": "vitest --ui" }, "dependencies": { - "@floating-ui/react-dom-interactions": "^0.6.6", - "@fortawesome/fontawesome-svg-core": "^1.2.32", - "@fortawesome/free-solid-svg-icons": "^5.15.1", - "@fortawesome/react-fontawesome": "^0.1.11", - "@headlessui/react": "^1.6.4", - "@heroicons/react": "^1.0.6", - "@hot-loader/react-dom": "^16.14.0", - "@preact/signals-react": "^1.2.1", - "@tailwindcss/forms": "^0.5.2", - "@tailwindcss/line-clamp": "^0.4.0", - "axios": "^0.27.2", - "boring-avatars": "^1.7.0", - "chart.js": "^3.8.0", - "classnames": "^2.3.1", - "codemirror": "^5.57.0", - "copy-to-clipboard": "^3.3.1", - "date-fns": "^2.28.0", - "debounce": "^1.2.0", - "deepmerge-ts": "^4.2.1", - "easy-peasy": "^4.0.1", - "events": "^3.0.0", - "formik": "^2.2.6", - "framer-motion": "^6.3.10", - "i18next": "^21.8.9", - "i18next-http-backend": "^1.4.1", - "i18next-multiload-backend-adapter": "^1.0.0", - "qrcode.react": "^1.0.1", - "react": "^16.14.0", - "react-chartjs-2": "^4.2.0", - "react-dom": "npm:@hot-loader/react-dom", - "react-fast-compare": "^3.2.0", - "react-hot-loader": "^4.12.21", - "react-i18next": "^11.2.1", - "react-router-dom": "^5.1.2", - "react-transition-group": "^4.4.1", - "reaptcha": "^1.7.2", - "sockette": "^2.0.6", - "styled-components": "^5.2.1", - "styled-components-breakpoint": "^3.0.0-preview.20", - "swr": "^0.2.3", - "tailwindcss": "^3.0.24", - "use-fit-text": "^2.4.0", - "uuid": "^8.3.2", - "xterm": "^4.19.0", - "xterm-addon-fit": "^0.5.0", - "xterm-addon-search": "^0.9.0", - "xterm-addon-search-bar": "^0.2.0", - "xterm-addon-web-links": "^0.6.0", - "yup": "^0.29.1" + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/commands": "^6.0.0", + "@codemirror/lang-cpp": "^6.0.0", + "@codemirror/lang-css": "^6.0.0", + "@codemirror/lang-html": "^6.0.0", + "@codemirror/lang-java": "^6.0.0", + "@codemirror/lang-javascript": "^6.0.0", + "@codemirror/lang-json": "^6.0.0", + "@codemirror/lang-lezer": "^6.0.0", + "@codemirror/lang-markdown": "^6.0.0", + "@codemirror/lang-php": "^6.0.0", + "@codemirror/lang-python": "^6.0.0", + "@codemirror/lang-rust": "^6.0.0", + "@codemirror/lang-sql": "^6.0.0", + "@codemirror/lang-xml": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/language-data": "^6.0.0", + "@codemirror/legacy-modes": "^6.0.0", + "@codemirror/lint": "^6.0.0", + "@codemirror/search": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@floating-ui/react-dom-interactions": "0.10.2", + "@fortawesome/fontawesome-svg-core": "6.2.0", + "@fortawesome/free-solid-svg-icons": "6.2.0", + "@fortawesome/react-fontawesome": "0.2.0", + "@flyyer/use-fit-text": "3.0.1", + "@headlessui/react": "1.7.3", + "@heroicons/react": "1.0.6", + "@lezer/highlight": "1.1.2", + "@preact/signals-react": "1.1.1", + "@tailwindcss/forms": "0.5.3", + "@tailwindcss/line-clamp": "0.4.2", + "axios": "0.27.2", + "boring-avatars": "1.7.0", + "chart.js": "3.9.1", + "classnames": "2.3.2", + "codemirror": "5.57.0", + "copy-to-clipboard": "3.3.2", + "date-fns": "2.29.3", + "debounce": "1.2.1", + "deepmerge-ts": "4.2.2", + "easy-peasy": "5.1.0", + "events": "3.3.0", + "formik": "2.2.9", + "framer-motion": "7.6.2", + "i18next": "22.0.3", + "i18next-http-backend": "2.0.0", + "i18next-multiload-backend-adapter": "1.0.0", + "nanoid": "4.0.0", + "qrcode.react": "3.1.0", + "react": "18.2.0", + "react-chartjs-2": "4.2.0", + "react-dom": "18.2.0", + "react-fast-compare": "3.2.0", + "react-i18next": "12.0.0", + "react-router-dom": "6.4.2", + "reaptcha": "1.12.1", + "sockette": "2.0.6", + "styled-components": "5.3.6", + "styled-components-breakpoint": "3.0.0-preview.20", + "swr": "1.3.0", + "tailwindcss": "3.2.2", + "xterm": "5.0.0", + "xterm-addon-fit": "0.6.0", + "xterm-addon-search": "0.10.0", + "xterm-addon-search-bar": "0.2.0", + "xterm-addon-web-links": "0.7.0", + "yup": "0.32.11" }, "devDependencies": { - "@babel/core": "^7.12.1", - "@babel/plugin-proposal-class-properties": "^7.12.1", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.12.1", - "@babel/plugin-proposal-object-rest-spread": "^7.12.1", - "@babel/plugin-proposal-optional-chaining": "^7.12.1", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-transform-modules-commonjs": "^7.18.2", - "@babel/plugin-transform-react-jsx": "^7.12.1", - "@babel/plugin-transform-runtime": "^7.12.1", - "@babel/preset-env": "^7.12.1", - "@babel/preset-react": "^7.12.1", - "@babel/preset-typescript": "^7.12.1", - "@babel/runtime": "^7.12.1", - "@testing-library/dom": "^8.14.0", - "@testing-library/jest-dom": "^5.16.4", - "@testing-library/react": "12.1.5", - "@testing-library/user-event": "^14.2.1", - "@types/codemirror": "^0.0.98", - "@types/debounce": "^1.2.0", - "@types/events": "^3.0.0", - "@types/jest": "^28.1.3", - "@types/node": "^14.11.10", - "@types/qrcode.react": "^1.0.1", - "@types/react": "^16.14.0", - "@types/react-copy-to-clipboard": "^4.3.0", - "@types/react-dom": "^16.9.16", - "@types/react-redux": "^7.1.1", - "@types/react-router": "^5.1.3", - "@types/react-router-dom": "^5.1.3", - "@types/react-transition-group": "^4.4.0", - "@types/styled-components": "^5.1.7", - "@types/uuid": "^3.4.5", - "@types/webpack-env": "^1.15.2", - "@types/yup": "^0.29.3", - "@typescript-eslint/eslint-plugin": "^5.29.0", - "@typescript-eslint/parser": "^5.29.0", - "autoprefixer": "^10.4.7", - "babel-jest": "^28.1.1", - "babel-loader": "^8.2.5", - "babel-plugin-styled-components": "^2.0.7", - "cross-env": "^7.0.2", - "css-loader": "^5.2.7", - "eslint": "^8.18.0", - "eslint-config-prettier": "^8.5.0", - "eslint-plugin-jest-dom": "^4.0.2", - "eslint-plugin-node": "^11.1.0", - "eslint-plugin-prettier": "^4.0.0", - "eslint-plugin-react": "^7.30.1", - "eslint-plugin-react-hooks": "^4.6.0", - "fork-ts-checker-webpack-plugin": "^6.2.10", - "identity-obj-proxy": "^3.0.0", - "jest": "^28.1.1", - "postcss": "^8.4.14", - "postcss-import": "^14.1.0", - "postcss-loader": "^4.0.0", - "postcss-nesting": "^10.1.8", - "postcss-preset-env": "^7.7.1", - "prettier": "^2.7.1", - "redux-devtools-extension": "^2.13.8", - "source-map-loader": "^1.1.3", - "style-loader": "^2.0.0", - "svg-url-loader": "^7.1.1", - "terser-webpack-plugin": "^4.2.3", - "ts-essentials": "^9.1.2", - "ts-jest": "^28.0.5", - "twin.macro": "^2.8.2", - "typescript": "^4.7.3", - "webpack": "^4.43.0", - "webpack-assets-manifest": "^3.1.1", - "webpack-bundle-analyzer": "^3.8.0", - "webpack-cli": "^3.3.12", - "webpack-dev-server": "^3.11.0", - "yarn-deduplicate": "^1.1.1" - }, - "scripts": { - "clean": "cd public/assets && find . \\( -name \"*.js\" -o -name \"*.map\" \\) -type f -delete", - "test": "jest", - "lint": "eslint ./resources/scripts/**/*.{ts,tsx} --ext .ts,.tsx", - "watch": "cross-env NODE_ENV=development ./node_modules/.bin/webpack --watch --progress", - "build": "cross-env NODE_ENV=development ./node_modules/.bin/webpack --progress", - "build:production": "yarn run clean && cross-env NODE_ENV=production ./node_modules/.bin/webpack --mode production", - "serve": "yarn run clean && cross-env WEBPACK_PUBLIC_PATH=/webpack@hmr/ NODE_ENV=development webpack-dev-server --host 0.0.0.0 --port 8080 --public https://pterodactyl.test --hot" + "@testing-library/dom": "8.19.0", + "@testing-library/react": "13.4.0", + "@testing-library/user-event": "14.4.3", + "@types/codemirror": "0.0.109", + "@types/debounce": "1.2.1", + "@types/events": "3.0.0", + "@types/node": "18.11.9", + "@types/react": "18.0.24", + "@types/react-dom": "18.0.8", + "@types/styled-components": "5.1.26", + "@typescript-eslint/eslint-plugin": "5.41.0", + "@typescript-eslint/parser": "5.41.0", + "@vitejs/plugin-react": "2.2.0", + "autoprefixer": "10.4.12", + "babel-plugin-styled-components": "2.0.7", + "babel-plugin-twin": "1.0.2", + "cross-env": "7.0.3", + "eslint": "8.18.0", + "eslint-config-prettier": "8.5.0", + "eslint-plugin-node": "11.1.0", + "eslint-plugin-prettier": "4.2.1", + "eslint-plugin-react": "7.31.10", + "eslint-plugin-react-hooks": "4.6.0", + "happy-dom": "7.6.6", + "laravel-vite-plugin": "0.7.0", + "pathe": "0.3.9", + "postcss": "8.4.18", + "postcss-import": "15.0.0", + "postcss-nesting": "10.2.0", + "postcss-preset-env": "7.8.2", + "prettier": "2.7.1", + "rimraf": "3.0.2", + "ts-essentials": "9.3.0", + "twin.macro": "2.8.2", + "typescript": "4.8.4", + "vite": "3.2.2", + "vitest": "0.24.5" }, "browserslist": [ "> 0.5%", @@ -146,6 +132,7 @@ ], "babelMacros": { "twin": { + "config": "tailwind.config.js", "preset": "styled-components" }, "styledComponents": { diff --git a/postcss.config.js b/postcss.config.cjs similarity index 100% rename from postcss.config.js rename to postcss.config.cjs diff --git a/resources/scripts/TransitionRouter.tsx b/resources/scripts/TransitionRouter.tsx deleted file mode 100644 index 040097eaa8..0000000000 --- a/resources/scripts/TransitionRouter.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import React from 'react'; -import { Route } from 'react-router'; -import { SwitchTransition } from 'react-transition-group'; -import Fade from '@/components/elements/Fade'; -import styled from 'styled-components/macro'; -import tw from 'twin.macro'; - -const StyledSwitchTransition = styled(SwitchTransition)` - ${tw`relative`}; - - & section { - ${tw`absolute w-full top-0 left-0`}; - } -`; - -const TransitionRouter: React.FC = ({ children }) => { - return ( - ( - - -
{children}
-
-
- )} - /> - ); -}; - -export default TransitionRouter; diff --git a/resources/scripts/__mocks__/file.ts b/resources/scripts/__mocks__/file.ts deleted file mode 100644 index 86059f3629..0000000000 --- a/resources/scripts/__mocks__/file.ts +++ /dev/null @@ -1 +0,0 @@ -module.exports = 'test-file-stub'; diff --git a/resources/scripts/api/account/activity.ts b/resources/scripts/api/account/activity.ts index eef215696a..cbbec66f31 100644 --- a/resources/scripts/api/account/activity.ts +++ b/resources/scripts/api/account/activity.ts @@ -1,8 +1,10 @@ -import useSWR, { ConfigInterface, responseInterface } from 'swr'; -import { ActivityLog, Transformers } from '@definitions/user'; -import { AxiosError } from 'axios'; +import type { AxiosError } from 'axios'; +import type { SWRConfiguration } from 'swr'; +import useSWR from 'swr'; + import http, { PaginatedResult, QueryBuilderParams, withQueryBuilderParams } from '@/api/http'; import { toPaginatedSet } from '@definitions/helpers'; +import { ActivityLog, Transformers } from '@definitions/user'; import useFilteredObject from '@/plugins/useFilteredObject'; import { useUserSWRKey } from '@/plugins/useSWRKey'; @@ -10,8 +12,8 @@ export type ActivityLogFilters = QueryBuilderParams<'ip' | 'event', 'timestamp'> const useActivityLogs = ( filters?: ActivityLogFilters, - config?: ConfigInterface, AxiosError> -): responseInterface, AxiosError> => { + config?: SWRConfiguration, AxiosError>, +) => { const key = useUserSWRKey(['account', 'activity', JSON.stringify(useFilteredObject(filters || {}))]); return useSWR>( @@ -26,7 +28,7 @@ const useActivityLogs = ( return toPaginatedSet(data, Transformers.toActivityLog); }, - { revalidateOnMount: false, ...(config || {}) } + { revalidateOnMount: false, ...(config || {}) }, ); }; diff --git a/resources/scripts/api/account/createApiKey.ts b/resources/scripts/api/account/createApiKey.ts index b20760e62e..f2cf27b3ab 100644 --- a/resources/scripts/api/account/createApiKey.ts +++ b/resources/scripts/api/account/createApiKey.ts @@ -12,7 +12,7 @@ export default (description: string, allowedIps: string): Promise) => { +const useSSHKeys = (config?: SWRConfiguration) => { const key = useUserSWRKey(['account', 'ssh-keys']); return useSWR( @@ -16,7 +18,7 @@ const useSSHKeys = (config?: ConfigInterface) => { return Transformers.toSSHKey(datum.attributes); }); }, - { revalidateOnMount: false, ...(config || {}) } + { revalidateOnMount: false, ...(config || {}) }, ); }; diff --git a/resources/scripts/api/auth/login.ts b/resources/scripts/api/auth/login.ts index ff0de7ac62..e8d6cd75ee 100644 --- a/resources/scripts/api/auth/login.ts +++ b/resources/scripts/api/auth/login.ts @@ -20,9 +20,9 @@ export default ({ username, password, recaptchaData }: LoginData): Promise { + .then(response => { if (!(response.data instanceof Object)) { return reject(new Error('An error occurred while processing the login request.')); } diff --git a/resources/scripts/api/auth/loginCheckpoint.ts b/resources/scripts/api/auth/loginCheckpoint.ts index 73ffb21114..8e6c0c0703 100644 --- a/resources/scripts/api/auth/loginCheckpoint.ts +++ b/resources/scripts/api/auth/loginCheckpoint.ts @@ -8,11 +8,11 @@ export default (token: string, code: string, recoveryToken?: string): Promise 0 ? recoveryToken : undefined, }) - .then((response) => + .then(response => resolve({ complete: response.data.data.complete, intended: response.data.data.intended || undefined, - }) + }), ) .catch(reject); }); diff --git a/resources/scripts/api/auth/performPasswordReset.ts b/resources/scripts/api/auth/performPasswordReset.ts index 0dd12f4701..fb45dc4bc9 100644 --- a/resources/scripts/api/auth/performPasswordReset.ts +++ b/resources/scripts/api/auth/performPasswordReset.ts @@ -19,11 +19,11 @@ export default (email: string, data: Data): Promise => { password: data.password, password_confirmation: data.passwordConfirmation, }) - .then((response) => + .then(response => resolve({ redirectTo: response.data.redirect_to, sendToLogin: response.data.send_to_login, - }) + }), ) .catch(reject); }); diff --git a/resources/scripts/api/auth/requestPasswordResetEmail.ts b/resources/scripts/api/auth/requestPasswordResetEmail.ts index d68fa44472..2168160c2e 100644 --- a/resources/scripts/api/auth/requestPasswordResetEmail.ts +++ b/resources/scripts/api/auth/requestPasswordResetEmail.ts @@ -3,7 +3,7 @@ import http from '@/api/http'; export default (email: string, recaptchaData?: string): Promise => { return new Promise((resolve, reject) => { http.post('/auth/password', { email, 'g-recaptcha-response': recaptchaData }) - .then((response) => resolve(response.data.status || '')) + .then(response => resolve(response.data.status || '')) .catch(reject); }); }; diff --git a/resources/scripts/api/definitions/helpers.ts b/resources/scripts/api/definitions/helpers.ts index eeb933f5a4..8953608305 100644 --- a/resources/scripts/api/definitions/helpers.ts +++ b/resources/scripts/api/definitions/helpers.ts @@ -15,17 +15,17 @@ function transform(data: null | undefined, transformer: TransformerFunc function transform( data: FractalResponseData | null | undefined, transformer: TransformerFunc, - missing?: M + missing?: M, ): T | M; function transform( data: FractalResponseList | FractalPaginatedResponse | null | undefined, transformer: TransformerFunc, - missing?: M + missing?: M, ): T[] | M; function transform( data: FractalResponseData | FractalResponseList | FractalPaginatedResponse | null | undefined, transformer: TransformerFunc, - missing = undefined + missing = undefined, ) { if (data === undefined || data === null) { return missing; @@ -44,7 +44,7 @@ function transform( function toPaginatedSet>( response: FractalPaginatedResponse, - transformer: T + transformer: T, ): PaginatedResult> { return { items: transform(response, transformer) as ReturnType[], diff --git a/resources/scripts/api/getServers.ts b/resources/scripts/api/getServers.ts index 4e29f1532c..77a057f0d4 100644 --- a/resources/scripts/api/getServers.ts +++ b/resources/scripts/api/getServers.ts @@ -19,7 +19,7 @@ export default ({ query, ...params }: QueryParams): Promise rawDataToServerObject(datum)), pagination: getPaginationSet(data.meta.pagination), - }) + }), ) .catch(reject); }); diff --git a/resources/scripts/api/http.ts b/resources/scripts/api/http.ts index 382c1119a1..b2a5372622 100644 --- a/resources/scripts/api/http.ts +++ b/resources/scripts/api/http.ts @@ -11,7 +11,7 @@ const http: AxiosInstance = axios.create({ }, }); -http.interceptors.request.use((req) => { +http.interceptors.request.use(req => { if (!req.url?.endsWith('/resources')) { store.getActions().progress.startContinuous(); } @@ -20,18 +20,18 @@ http.interceptors.request.use((req) => { }); http.interceptors.response.use( - (resp) => { + resp => { if (!resp.request?.url?.endsWith('/resources')) { store.getActions().progress.setComplete(); } return resp; }, - (error) => { + error => { store.getActions().progress.setComplete(); throw error; - } + }, ); export default http; diff --git a/resources/scripts/api/interceptors.ts b/resources/scripts/api/interceptors.ts index 7b8ac87dae..c2380bc809 100644 --- a/resources/scripts/api/interceptors.ts +++ b/resources/scripts/api/interceptors.ts @@ -1,21 +1,22 @@ +import type { AxiosError } from 'axios'; +import type { NavigateFunction } from 'react-router-dom'; + import http from '@/api/http'; -import { AxiosError } from 'axios'; -import { History } from 'history'; -export const setupInterceptors = (history: History) => { +export const setupInterceptors = (navigate: NavigateFunction) => { http.interceptors.response.use( - (resp) => resp, + resp => resp, (error: AxiosError) => { if (error.response?.status === 400) { if ( (error.response?.data as Record).errors?.[0].code === 'TwoFactorAuthRequiredException' ) { if (!window.location.pathname.startsWith('/account')) { - history.replace('/account', { twoFactorRedirect: true }); + navigate('/account', { state: { twoFactorRedirect: true } }); } } } throw error; - } + }, ); }; diff --git a/resources/scripts/api/server/activity.ts b/resources/scripts/api/server/activity.ts index a7fa8d31bf..5476a582c5 100644 --- a/resources/scripts/api/server/activity.ts +++ b/resources/scripts/api/server/activity.ts @@ -1,8 +1,12 @@ -import useSWR, { ConfigInterface, responseInterface } from 'swr'; -import { ActivityLog, Transformers } from '@definitions/user'; -import { AxiosError } from 'axios'; -import http, { PaginatedResult, QueryBuilderParams, withQueryBuilderParams } from '@/api/http'; +import type { AxiosError } from 'axios'; +import type { SWRConfiguration } from 'swr'; +import useSWR from 'swr'; + +import type { PaginatedResult, QueryBuilderParams } from '@/api/http'; +import http, { withQueryBuilderParams } from '@/api/http'; import { toPaginatedSet } from '@definitions/helpers'; +import type { ActivityLog } from '@definitions/user'; +import { Transformers } from '@definitions/user'; import useFilteredObject from '@/plugins/useFilteredObject'; import { useServerSWRKey } from '@/plugins/useSWRKey'; import { ServerContext } from '@/state/server'; @@ -11,9 +15,9 @@ export type ActivityLogFilters = QueryBuilderParams<'ip' | 'event', 'timestamp'> const useActivityLogs = ( filters?: ActivityLogFilters, - config?: ConfigInterface, AxiosError> -): responseInterface, AxiosError> => { - const uuid = ServerContext.useStoreState((state) => state.server.data?.uuid); + config?: SWRConfiguration, AxiosError>, +) => { + const uuid = ServerContext.useStoreState(state => state.server.data?.uuid); const key = useServerSWRKey(['activity', useFilteredObject(filters || {})]); return useSWR>( @@ -28,7 +32,7 @@ const useActivityLogs = ( return toPaginatedSet(data, Transformers.toActivityLog); }, - { revalidateOnMount: false, ...(config || {}) } + { revalidateOnMount: false, ...(config || {}) }, ); }; diff --git a/resources/scripts/api/server/databases/createServerDatabase.ts b/resources/scripts/api/server/databases/createServerDatabase.ts index cb0c25b9e5..8d5f5c4427 100644 --- a/resources/scripts/api/server/databases/createServerDatabase.ts +++ b/resources/scripts/api/server/databases/createServerDatabase.ts @@ -11,9 +11,9 @@ export default (uuid: string, data: { connectionsFrom: string; databaseName: str }, { params: { include: 'password' }, - } + }, ) - .then((response) => resolve(rawDataToServerDatabase(response.data.attributes))) + .then(response => resolve(rawDataToServerDatabase(response.data.attributes))) .catch(reject); }); }; diff --git a/resources/scripts/api/server/databases/getServerDatabases.ts b/resources/scripts/api/server/databases/getServerDatabases.ts index a03eb30a99..32437d0638 100644 --- a/resources/scripts/api/server/databases/getServerDatabases.ts +++ b/resources/scripts/api/server/databases/getServerDatabases.ts @@ -23,8 +23,8 @@ export default (uuid: string, includePassword = true): Promise http.get(`/api/client/servers/${uuid}/databases`, { params: includePassword ? { include: 'password' } : undefined, }) - .then((response) => - resolve((response.data.data || []).map((item: any) => rawDataToServerDatabase(item.attributes))) + .then(response => + resolve((response.data.data || []).map((item: any) => rawDataToServerDatabase(item.attributes))), ) .catch(reject); }); diff --git a/resources/scripts/api/server/databases/rotateDatabasePassword.ts b/resources/scripts/api/server/databases/rotateDatabasePassword.ts index 0e0619a84f..a7d299c02d 100644 --- a/resources/scripts/api/server/databases/rotateDatabasePassword.ts +++ b/resources/scripts/api/server/databases/rotateDatabasePassword.ts @@ -4,7 +4,7 @@ import http from '@/api/http'; export default (uuid: string, database: string): Promise => { return new Promise((resolve, reject) => { http.post(`/api/client/servers/${uuid}/databases/${database}/rotate-password`) - .then((response) => resolve(rawDataToServerDatabase(response.data.attributes))) + .then(response => resolve(rawDataToServerDatabase(response.data.attributes))) .catch(reject); }); }; diff --git a/resources/scripts/api/server/files/compressFiles.ts b/resources/scripts/api/server/files/compressFiles.ts index b4c0a251ec..0f78826522 100644 --- a/resources/scripts/api/server/files/compressFiles.ts +++ b/resources/scripts/api/server/files/compressFiles.ts @@ -10,7 +10,7 @@ export default async (uuid: string, directory: string, files: string[]): Promise timeout: 60000, timeoutErrorMessage: 'It looks like this archive is taking a long time to generate. It will appear once completed.', - } + }, ); return rawDataToFileObject(data); diff --git a/resources/scripts/api/server/files/decompressFiles.ts b/resources/scripts/api/server/files/decompressFiles.ts index 37557a671c..d3a3e45c29 100644 --- a/resources/scripts/api/server/files/decompressFiles.ts +++ b/resources/scripts/api/server/files/decompressFiles.ts @@ -8,6 +8,6 @@ export default async (uuid: string, directory: string, file: string): Promise => { return new Promise((resolve, reject) => { http.get(`/api/client/servers/${server}/files/contents`, { params: { file }, - transformResponse: (res) => res, + transformResponse: res => res, responseType: 'text', }) .then(({ data }) => resolve(data)) diff --git a/resources/scripts/api/server/getServer.ts b/resources/scripts/api/server/getServer.ts index d2aa2e0543..6c1034071c 100644 --- a/resources/scripts/api/server/getServer.ts +++ b/resources/scripts/api/server/getServer.ts @@ -25,7 +25,7 @@ export interface Server { }; invocation: string; dockerImage: string; - description: string; + description: string | null; limits: { memory: number; swap: number; @@ -65,10 +65,10 @@ export const rawDataToServerObject = ({ attributes: data }: FractalResponseData) featureLimits: { ...data.feature_limits }, isTransferring: data.is_transferring, variables: ((data.relationships?.variables as FractalResponseList | undefined)?.data || []).map( - rawDataToServerEggVariable + rawDataToServerEggVariable, ), allocations: ((data.relationships?.allocations as FractalResponseList | undefined)?.data || []).map( - rawDataToServerAllocation + rawDataToServerAllocation, ), }); @@ -80,7 +80,7 @@ export default (uuid: string): Promise<[Server, string[]]> => { rawDataToServerObject(data), // eslint-disable-next-line camelcase data.meta?.is_server_owner ? ['*'] : data.meta?.user_permissions || [], - ]) + ]), ) .catch(reject); }); diff --git a/resources/scripts/api/server/getServerResourceUsage.ts b/resources/scripts/api/server/getServerResourceUsage.ts index 200ceb0179..dd1ad43077 100644 --- a/resources/scripts/api/server/getServerResourceUsage.ts +++ b/resources/scripts/api/server/getServerResourceUsage.ts @@ -26,7 +26,7 @@ export default (server: string): Promise => { networkRxInBytes: attributes.resources.network_rx_bytes, networkTxInBytes: attributes.resources.network_tx_bytes, uptime: attributes.resources.uptime, - }) + }), ) .catch(reject); }); diff --git a/resources/scripts/api/server/getWebsocketToken.ts b/resources/scripts/api/server/getWebsocketToken.ts index 4769e188b9..5b2994b22e 100644 --- a/resources/scripts/api/server/getWebsocketToken.ts +++ b/resources/scripts/api/server/getWebsocketToken.ts @@ -12,7 +12,7 @@ export default (server: string): Promise => { resolve({ token: data.data.token, socket: data.data.socket, - }) + }), ) .catch(reject); }); diff --git a/resources/scripts/api/server/schedules/createOrUpdateScheduleTask.ts b/resources/scripts/api/server/schedules/createOrUpdateScheduleTask.ts index 388d8d6d58..e0df704362 100644 --- a/resources/scripts/api/server/schedules/createOrUpdateScheduleTask.ts +++ b/resources/scripts/api/server/schedules/createOrUpdateScheduleTask.ts @@ -16,7 +16,7 @@ export default async (uuid: string, schedule: number, task: number | undefined, payload: data.payload, continue_on_failure: data.continueOnFailure, time_offset: data.timeOffset, - } + }, ); return rawDataToServerTask(response.attributes); diff --git a/resources/scripts/api/server/users/createOrUpdateSubuser.ts b/resources/scripts/api/server/users/createOrUpdateSubuser.ts index 019d35c7aa..93303a2db2 100644 --- a/resources/scripts/api/server/users/createOrUpdateSubuser.ts +++ b/resources/scripts/api/server/users/createOrUpdateSubuser.ts @@ -12,7 +12,7 @@ export default (uuid: string, params: Params, subuser?: Subuser): Promise resolve(rawDataToServerSubuser(data.data))) + .then(data => resolve(rawDataToServerSubuser(data.data))) .catch(reject); }); }; diff --git a/resources/scripts/api/server/users/getServerSubusers.ts b/resources/scripts/api/server/users/getServerSubusers.ts index dae2ce5806..177bde815f 100644 --- a/resources/scripts/api/server/users/getServerSubusers.ts +++ b/resources/scripts/api/server/users/getServerSubusers.ts @@ -9,7 +9,7 @@ export const rawDataToServerSubuser = (data: FractalResponseData): Subuser => ({ twoFactorEnabled: data.attributes['2fa_enabled'], createdAt: new Date(data.attributes.created_at), permissions: data.attributes.permissions || [], - can: (permission) => (data.attributes.permissions || []).indexOf(permission) >= 0, + can: permission => (data.attributes.permissions || []).indexOf(permission) >= 0, }); export default (uuid: string): Promise => { diff --git a/resources/scripts/api/swr/getServerAllocations.ts b/resources/scripts/api/swr/getServerAllocations.ts index b254b6505c..0936550ee7 100644 --- a/resources/scripts/api/swr/getServerAllocations.ts +++ b/resources/scripts/api/swr/getServerAllocations.ts @@ -1,11 +1,12 @@ -import { ServerContext } from '@/state/server'; import useSWR from 'swr'; + import http from '@/api/http'; -import { rawDataToServerAllocation } from '@/api/transformers'; import { Allocation } from '@/api/server/getServer'; +import { rawDataToServerAllocation } from '@/api/transformers'; +import { ServerContext } from '@/state/server'; export default () => { - const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid); + const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); return useSWR( ['server:allocations', uuid], @@ -14,6 +15,6 @@ export default () => { return (data.data || []).map(rawDataToServerAllocation); }, - { revalidateOnFocus: false, revalidateOnMount: false } + { revalidateOnFocus: false, revalidateOnMount: false }, ); }; diff --git a/resources/scripts/api/swr/getServerBackups.ts b/resources/scripts/api/swr/getServerBackups.ts index dbc7f5d989..596105816c 100644 --- a/resources/scripts/api/swr/getServerBackups.ts +++ b/resources/scripts/api/swr/getServerBackups.ts @@ -1,9 +1,11 @@ +import { createContext, useContext } from 'react'; import useSWR from 'swr'; -import http, { getPaginationSet, PaginatedResult } from '@/api/http'; -import { ServerBackup } from '@/api/server/types'; + +import type { PaginatedResult } from '@/api/http'; +import http, { getPaginationSet } from '@/api/http'; +import type { ServerBackup } from '@/api/server/types'; import { rawDataToServerBackup } from '@/api/transformers'; import { ServerContext } from '@/state/server'; -import { createContext, useContext } from 'react'; interface ctx { page: number; @@ -16,7 +18,7 @@ type BackupResponse = PaginatedResult & { backupCount: number }; export default () => { const { page } = useContext(Context); - const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid); + const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); return useSWR(['server:backups', uuid, page], async () => { const { data } = await http.get(`/api/client/servers/${uuid}/backups`, { params: { page } }); diff --git a/resources/scripts/api/swr/getServerStartup.ts b/resources/scripts/api/swr/getServerStartup.ts index efecefe08d..df05bdb3fc 100644 --- a/resources/scripts/api/swr/getServerStartup.ts +++ b/resources/scripts/api/swr/getServerStartup.ts @@ -1,7 +1,10 @@ -import useSWR, { ConfigInterface } from 'swr'; +import type { AxiosError } from 'axios'; +import type { SWRConfiguration } from 'swr'; +import useSWR from 'swr'; + import http, { FractalResponseList } from '@/api/http'; +import type { ServerEggVariable } from '@/api/server/types'; import { rawDataToServerEggVariable } from '@/api/transformers'; -import { ServerEggVariable } from '@/api/server/types'; interface Response { invocation: string; @@ -9,7 +12,7 @@ interface Response { dockerImages: Record; } -export default (uuid: string, initialData?: Response | null, config?: ConfigInterface) => +export default (uuid: string, fallbackData?: Response, config?: SWRConfiguration) => useSWR( [uuid, '/startup'], async (): Promise => { @@ -23,5 +26,5 @@ export default (uuid: string, initialData?: Response | null, config?: ConfigInte dockerImages: data.meta.docker_images || {}, }; }, - { initialData: initialData || undefined, errorRetryCount: 3, ...(config || {}) } + { fallbackData, errorRetryCount: 3, ...(config ?? {}) }, ); diff --git a/resources/scripts/api/transformers.ts b/resources/scripts/api/transformers.ts index 4ed9a5df5a..34a2611d0b 100644 --- a/resources/scripts/api/transformers.ts +++ b/resources/scripts/api/transformers.ts @@ -49,7 +49,7 @@ export const rawDataToFileObject = (data: FractalResponseData): FileObject => ({ const matches = ['application/jar', 'application/octet-stream', 'inode/directory', /^image\//]; - return matches.every((m) => !this.mimetype.match(m)); + return matches.every(m => !this.mimetype.match(m)); }, }); diff --git a/resources/scripts/assets/css/GlobalStylesheet.ts b/resources/scripts/assets/css/GlobalStylesheet.ts index 7f520be458..4ae89abc4b 100644 --- a/resources/scripts/assets/css/GlobalStylesheet.ts +++ b/resources/scripts/assets/css/GlobalStylesheet.ts @@ -1,5 +1,5 @@ import tw from 'twin.macro'; -import { createGlobalStyle } from 'styled-components/macro'; +import { createGlobalStyle } from 'styled-components'; export default createGlobalStyle` body { diff --git a/resources/scripts/components/App.tsx b/resources/scripts/components/App.tsx index 935400030b..c72ce426f5 100644 --- a/resources/scripts/components/App.tsx +++ b/resources/scripts/components/App.tsx @@ -1,23 +1,20 @@ -import React, { lazy } from 'react'; -import { hot } from 'react-hot-loader/root'; -import { Route, Router, Switch } from 'react-router-dom'; import { StoreProvider } from 'easy-peasy'; -import { store } from '@/state'; -import { SiteSettings } from '@/state/settings'; -import ProgressBar from '@/components/elements/ProgressBar'; -import { NotFound } from '@/components/elements/ScreenBlock'; -import tw from 'twin.macro'; +import { lazy } from 'react'; +import { BrowserRouter, Route, Routes } from 'react-router-dom'; + +import '@/assets/tailwind.css'; import GlobalStylesheet from '@/assets/css/GlobalStylesheet'; -import { history } from '@/components/history'; -import { setupInterceptors } from '@/api/interceptors'; import AuthenticatedRoute from '@/components/elements/AuthenticatedRoute'; -import { ServerContext } from '@/state/server'; -import '@/assets/tailwind.css'; +import ProgressBar from '@/components/elements/ProgressBar'; +import { NotFound } from '@/components/elements/ScreenBlock'; import Spinner from '@/components/elements/Spinner'; +import { store } from '@/state'; +import { ServerContext } from '@/state/server'; +import { SiteSettings } from '@/state/settings'; -const DashboardRouter = lazy(() => import(/* webpackChunkName: "dashboard" */ '@/routers/DashboardRouter')); -const ServerRouter = lazy(() => import(/* webpackChunkName: "server" */ '@/routers/ServerRouter')); -const AuthenticationRouter = lazy(() => import(/* webpackChunkName: "auth" */ '@/routers/AuthenticationRouter')); +const DashboardRouter = lazy(() => import('@/routers/DashboardRouter')); +const ServerRouter = lazy(() => import('@/routers/ServerRouter')); +const AuthenticationRouter = lazy(() => import('@/routers/AuthenticationRouter')); interface ExtendedWindow extends Window { SiteConfiguration?: SiteSettings; @@ -35,9 +32,9 @@ interface ExtendedWindow extends Window { }; } -setupInterceptors(history); +// setupInterceptors(history); -const App = () => { +function App() { const { PterodactylUser, SiteConfiguration } = window as ExtendedWindow; if (PterodactylUser && !store.getState().user.data) { store.getActions().user.setUserData({ @@ -58,38 +55,55 @@ const App = () => { return ( <> + {/* @ts-expect-error go away */} + -
- - - - - - - - - - - - - - - - - - - - - - - - + +
+ + + + + + } + /> + + + + + + + + + } + /> + + + + + + + } + /> + + } /> + +
); -}; +} -export default hot(App); +export { App }; diff --git a/resources/scripts/components/Avatar.tsx b/resources/scripts/components/Avatar.tsx index 4d9d254dbe..5ba0489a7e 100644 --- a/resources/scripts/components/Avatar.tsx +++ b/resources/scripts/components/Avatar.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import BoringAvatar, { AvatarProps } from 'boring-avatars'; import { useStoreState } from '@/state/hooks'; @@ -11,7 +10,7 @@ const _Avatar = ({ variant = 'beam', ...props }: AvatarProps) => ( ); const _UserAvatar = ({ variant = 'beam', ...props }: Omit) => { - const uuid = useStoreState((state) => state.user.data?.uuid); + const uuid = useStoreState(state => state.user.data?.uuid); return ; }; diff --git a/resources/scripts/components/FlashMessageRender.tsx b/resources/scripts/components/FlashMessageRender.tsx index 8d0b43f2b7..e7cf7726d3 100644 --- a/resources/scripts/components/FlashMessageRender.tsx +++ b/resources/scripts/components/FlashMessageRender.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import { Fragment } from 'react'; import MessageBox from '@/components/MessageBox'; import { useStoreState } from 'easy-peasy'; import tw from 'twin.macro'; @@ -9,19 +9,17 @@ type Props = Readonly<{ }>; const FlashMessageRender = ({ byKey, className }: Props) => { - const flashes = useStoreState((state) => - state.flashes.items.filter((flash) => (byKey ? flash.key === byKey : true)) - ); + const flashes = useStoreState(state => state.flashes.items.filter(flash => (byKey ? flash.key === byKey : true))); return flashes.length ? (
{flashes.map((flash, index) => ( - + {index > 0 &&
} {flash.message} -
+ ))}
) : null; diff --git a/resources/scripts/components/MessageBox.tsx b/resources/scripts/components/MessageBox.tsx index 57a5cacb5b..482ee392b9 100644 --- a/resources/scripts/components/MessageBox.tsx +++ b/resources/scripts/components/MessageBox.tsx @@ -1,6 +1,5 @@ -import * as React from 'react'; import tw, { TwStyle } from 'twin.macro'; -import styled from 'styled-components/macro'; +import styled from 'styled-components'; export type FlashMessageType = 'success' | 'info' | 'warning' | 'error'; @@ -42,7 +41,7 @@ const getBackground = (type?: FlashMessageType): TwStyle | string => { const Container = styled.div<{ $type?: FlashMessageType }>` ${tw`p-2 border items-center leading-normal rounded flex w-full text-sm text-white`}; - ${(props) => styling(props.$type)}; + ${props => styling(props.$type)}; `; Container.displayName = 'MessageBox.Container'; diff --git a/resources/scripts/components/NavigationBar.tsx b/resources/scripts/components/NavigationBar.tsx index 89180b14b8..954d6d4cdb 100644 --- a/resources/scripts/components/NavigationBar.tsx +++ b/resources/scripts/components/NavigationBar.tsx @@ -1,4 +1,3 @@ -import * as React from 'react'; import { useState } from 'react'; import { Link, NavLink } from 'react-router-dom'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; @@ -7,7 +6,7 @@ import { useStoreState } from 'easy-peasy'; import { ApplicationStore } from '@/state'; import SearchContainer from '@/components/dashboard/search/SearchContainer'; import tw, { theme } from 'twin.macro'; -import styled from 'styled-components/macro'; +import styled from 'styled-components'; import http from '@/api/http'; import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; import Tooltip from '@/components/elements/tooltip/Tooltip'; @@ -39,6 +38,7 @@ export default () => { const onTriggerLogout = () => { setIsLoggingOut(true); + http.post('/auth/logout').finally(() => { // @ts-expect-error this is valid window.location = '/'; @@ -46,41 +46,43 @@ export default () => { }; return ( -
+
-
-
+
+ - + - - + + + + {rootAdmin && ( - - + + )} - - - + + + + - + + diff --git a/resources/scripts/components/auth/ForgotPasswordContainer.tsx b/resources/scripts/components/auth/ForgotPasswordContainer.tsx index 76ddf19924..694cfce780 100644 --- a/resources/scripts/components/auth/ForgotPasswordContainer.tsx +++ b/resources/scripts/components/auth/ForgotPasswordContainer.tsx @@ -1,28 +1,29 @@ -import * as React from 'react'; +import { useStoreState } from 'easy-peasy'; +import type { FormikHelpers } from 'formik'; +import { Formik } from 'formik'; import { useEffect, useRef, useState } from 'react'; import { Link } from 'react-router-dom'; +import Reaptcha from 'reaptcha'; +import tw from 'twin.macro'; +import { object, string } from 'yup'; + import requestPasswordResetEmail from '@/api/auth/requestPasswordResetEmail'; import { httpErrorToHuman } from '@/api/http'; import LoginFormContainer from '@/components/auth/LoginFormContainer'; -import { useStoreState } from 'easy-peasy'; -import Field from '@/components/elements/Field'; -import { Formik, FormikHelpers } from 'formik'; -import { object, string } from 'yup'; -import tw from 'twin.macro'; import Button from '@/components/elements/Button'; -import Reaptcha from 'reaptcha'; +import Field from '@/components/elements/Field'; import useFlash from '@/plugins/useFlash'; interface Values { email: string; } -export default () => { +function ForgotPasswordContainer() { const ref = useRef(null); const [token, setToken] = useState(''); const { clearFlashes, addFlash } = useFlash(); - const { enabled: recaptchaEnabled, siteKey } = useStoreState((state) => state.settings.data!.recaptcha); + const { enabled: recaptchaEnabled, siteKey } = useStoreState(state => state.settings.data!.recaptcha); useEffect(() => { clearFlashes(); @@ -34,7 +35,7 @@ export default () => { // If there is no token in the state yet, request the token and then abort this submit request // since it will be re-submitted when the recaptcha data is returned by the component. if (recaptchaEnabled && !token) { - ref.current!.execute().catch((error) => { + ref.current!.execute().catch(error => { console.error(error); setSubmitting(false); @@ -45,17 +46,19 @@ export default () => { } requestPasswordResetEmail(email, token) - .then((response) => { + .then(response => { resetForm(); addFlash({ type: 'success', title: 'Success', message: response }); }) - .catch((error) => { + .catch(error => { console.error(error); addFlash({ type: 'error', title: 'Error', message: httpErrorToHuman(error) }); }) .then(() => { setToken(''); - if (ref.current) ref.current.reset(); + if (ref.current !== null) { + void ref.current.reset(); + } setSubmitting(false); }); @@ -92,9 +95,9 @@ export default () => { ref={ref} size={'invisible'} sitekey={siteKey || '_invalid_key'} - onVerify={(response) => { + onVerify={response => { setToken(response); - submitForm(); + void submitForm(); }} onExpire={() => { setSubmitting(false); @@ -114,4 +117,6 @@ export default () => { )} ); -}; +} + +export default ForgotPasswordContainer; diff --git a/resources/scripts/components/auth/LoginCheckpointContainer.tsx b/resources/scripts/components/auth/LoginCheckpointContainer.tsx index 66a6fb8f44..a5c59ee4b6 100644 --- a/resources/scripts/components/auth/LoginCheckpointContainer.tsx +++ b/resources/scripts/components/auth/LoginCheckpointContainer.tsx @@ -1,28 +1,29 @@ -import React, { useState } from 'react'; -import { Link, RouteComponentProps } from 'react-router-dom'; -import loginCheckpoint from '@/api/auth/loginCheckpoint'; -import LoginFormContainer from '@/components/auth/LoginFormContainer'; -import { ActionCreator } from 'easy-peasy'; -import { StaticContext } from 'react-router'; +import type { ActionCreator } from 'easy-peasy'; import { useFormikContext, withFormik } from 'formik'; -import useFlash from '@/plugins/useFlash'; -import { FlashStore } from '@/state/flashes'; -import Field from '@/components/elements/Field'; +import { useState } from 'react'; +import type { Location, RouteProps } from 'react-router-dom'; +import { Link, useLocation, useNavigate } from 'react-router-dom'; import tw from 'twin.macro'; + +import loginCheckpoint from '@/api/auth/loginCheckpoint'; +import LoginFormContainer from '@/components/auth/LoginFormContainer'; import Button from '@/components/elements/Button'; +import Field from '@/components/elements/Field'; +import useFlash from '@/plugins/useFlash'; +import type { FlashStore } from '@/state/flashes'; interface Values { code: string; recoveryCode: ''; } -type OwnProps = RouteComponentProps, StaticContext, { token?: string }>; +type OwnProps = RouteProps; type Props = OwnProps & { clearAndAddHttpError: ActionCreator; }; -const LoginCheckpointContainer = () => { +function LoginCheckpointContainer() { const { isSubmitting, setFieldValue } = useFormikContext(); const [isMissingDevice, setIsMissingDevice] = useState(false); @@ -53,7 +54,7 @@ const LoginCheckpointContainer = () => { onClick={() => { setFieldValue('code', ''); setFieldValue('recoveryCode', ''); - setIsMissingDevice((s) => !s); + setIsMissingDevice(s => !s); }} css={tw`cursor-pointer text-xs text-neutral-500 tracking-wide uppercase no-underline hover:text-neutral-700`} > @@ -70,12 +71,12 @@ const LoginCheckpointContainer = () => {
); -}; +} -const EnhancedForm = withFormik({ +const EnhancedForm = withFormik({ handleSubmit: ({ code, recoveryCode }, { setSubmitting, props: { clearAndAddHttpError, location } }) => { loginCheckpoint(location.state?.token || '', code, recoveryCode) - .then((response) => { + .then(response => { if (response.complete) { // @ts-expect-error this is valid window.location = response.intended || '/'; @@ -84,7 +85,7 @@ const EnhancedForm = withFormik({ setSubmitting(false); }) - .catch((error) => { + .catch(error => { console.error(error); setSubmitting(false); clearAndAddHttpError({ error }); @@ -97,16 +98,17 @@ const EnhancedForm = withFormik({ }), })(LoginCheckpointContainer); -export default ({ history, location, ...props }: OwnProps) => { +export default ({ ...props }: OwnProps) => { const { clearAndAddHttpError } = useFlash(); + const location = useLocation(); + const navigate = useNavigate(); + if (!location.state?.token) { - history.replace('/auth/login'); + navigate('/auth/login'); return null; } - return ( - - ); + return ; }; diff --git a/resources/scripts/components/auth/LoginContainer.tsx b/resources/scripts/components/auth/LoginContainer.tsx index 453a27ecc3..0cb5aae16f 100644 --- a/resources/scripts/components/auth/LoginContainer.tsx +++ b/resources/scripts/components/auth/LoginContainer.tsx @@ -1,14 +1,16 @@ -import React, { useEffect, useRef, useState } from 'react'; -import { Link, RouteComponentProps } from 'react-router-dom'; -import login from '@/api/auth/login'; -import LoginFormContainer from '@/components/auth/LoginFormContainer'; import { useStoreState } from 'easy-peasy'; -import { Formik, FormikHelpers } from 'formik'; +import type { FormikHelpers } from 'formik'; +import { Formik } from 'formik'; +import { useEffect, useRef, useState } from 'react'; +import { Link, useNavigate } from 'react-router-dom'; +import Reaptcha from 'reaptcha'; +import tw from 'twin.macro'; import { object, string } from 'yup'; + +import login from '@/api/auth/login'; +import LoginFormContainer from '@/components/auth/LoginFormContainer'; import Field from '@/components/elements/Field'; -import tw from 'twin.macro'; import Button from '@/components/elements/Button'; -import Reaptcha from 'reaptcha'; import useFlash from '@/plugins/useFlash'; interface Values { @@ -16,12 +18,14 @@ interface Values { password: string; } -const LoginContainer = ({ history }: RouteComponentProps) => { +function LoginContainer() { const ref = useRef(null); const [token, setToken] = useState(''); const { clearFlashes, clearAndAddHttpError } = useFlash(); - const { enabled: recaptchaEnabled, siteKey } = useStoreState((state) => state.settings.data!.recaptcha); + const { enabled: recaptchaEnabled, siteKey } = useStoreState(state => state.settings.data!.recaptcha); + + const navigate = useNavigate(); useEffect(() => { clearFlashes(); @@ -33,7 +37,7 @@ const LoginContainer = ({ history }: RouteComponentProps) => { // If there is no token in the state yet, request the token and then abort this submit request // since it will be re-submitted when the recaptcha data is returned by the component. if (recaptchaEnabled && !token) { - ref.current!.execute().catch((error) => { + ref.current!.execute().catch(error => { console.error(error); setSubmitting(false); @@ -44,16 +48,16 @@ const LoginContainer = ({ history }: RouteComponentProps) => { } login({ ...values, recaptchaData: token }) - .then((response) => { + .then(response => { if (response.complete) { // @ts-expect-error this is valid window.location = response.intended || '/'; return; } - history.replace('/auth/login/checkpoint', { token: response.confirmationToken }); + navigate('/auth/login/checkpoint', { state: { token: response.confirmationToken } }); }) - .catch((error) => { + .catch(error => { console.error(error); setToken(''); @@ -89,7 +93,7 @@ const LoginContainer = ({ history }: RouteComponentProps) => { ref={ref} size={'invisible'} sitekey={siteKey || '_invalid_key'} - onVerify={(response) => { + onVerify={response => { setToken(response); submitForm(); }} @@ -111,6 +115,6 @@ const LoginContainer = ({ history }: RouteComponentProps) => { )} ); -}; +} export default LoginContainer; diff --git a/resources/scripts/components/auth/LoginFormContainer.tsx b/resources/scripts/components/auth/LoginFormContainer.tsx index 62749815be..17060572be 100644 --- a/resources/scripts/components/auth/LoginFormContainer.tsx +++ b/resources/scripts/components/auth/LoginFormContainer.tsx @@ -1,6 +1,7 @@ -import React, { forwardRef } from 'react'; +import { forwardRef } from 'react'; +import * as React from 'react'; import { Form } from 'formik'; -import styled from 'styled-components/macro'; +import styled from 'styled-components'; import { breakpoint } from '@/theme'; import FlashMessageRender from '@/components/FlashMessageRender'; import tw from 'twin.macro'; diff --git a/resources/scripts/components/auth/ResetPasswordContainer.tsx b/resources/scripts/components/auth/ResetPasswordContainer.tsx index 86f95abf12..15a3b2090a 100644 --- a/resources/scripts/components/auth/ResetPasswordContainer.tsx +++ b/resources/scripts/components/auth/ResetPasswordContainer.tsx @@ -1,6 +1,5 @@ -import React, { useState } from 'react'; -import { RouteComponentProps } from 'react-router'; -import { Link } from 'react-router-dom'; +import { useState } from 'react'; +import { Link, useParams } from 'react-router-dom'; import performPasswordReset from '@/api/auth/performPasswordReset'; import { httpErrorToHuman } from '@/api/http'; import LoginFormContainer from '@/components/auth/LoginFormContainer'; @@ -18,7 +17,7 @@ interface Values { passwordConfirmation: string; } -export default ({ match, location }: RouteComponentProps<{ token: string }>) => { +function ResetPasswordContainer() { const [email, setEmail] = useState(''); const { clearFlashes, addFlash } = useStoreActions((actions: Actions) => actions.flashes); @@ -28,14 +27,16 @@ export default ({ match, location }: RouteComponentProps<{ token: string }>) => setEmail(parsed.get('email') || ''); } + const params = useParams<'token'>(); + const submit = ({ password, passwordConfirmation }: Values, { setSubmitting }: FormikHelpers) => { clearFlashes(); - performPasswordReset(email, { token: match.params.token, password, passwordConfirmation }) + performPasswordReset(email, { token: params.token ?? '', password, passwordConfirmation }) .then(() => { // @ts-expect-error this is valid window.location = '/'; }) - .catch((error) => { + .catch(error => { console.error(error); setSubmitting(false); @@ -56,7 +57,6 @@ export default ({ match, location }: RouteComponentProps<{ token: string }>) => .min(8, 'Your new password should be at least 8 characters in length.'), passwordConfirmation: string() .required('Your new password does not match.') - // @ts-expect-error this is valid .oneOf([ref('password'), null], 'Your new password does not match.'), })} > @@ -95,4 +95,6 @@ export default ({ match, location }: RouteComponentProps<{ token: string }>) => )} ); -}; +} + +export default ResetPasswordContainer; diff --git a/resources/scripts/components/dashboard/AccountApiContainer.tsx b/resources/scripts/components/dashboard/AccountApiContainer.tsx index 282f190929..645c79d6df 100644 --- a/resources/scripts/components/dashboard/AccountApiContainer.tsx +++ b/resources/scripts/components/dashboard/AccountApiContainer.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; import ContentBox from '@/components/elements/ContentBox'; import CreateApiKeyForm from '@/components/dashboard/forms/CreateApiKeyForm'; import getApiKeys, { ApiKey } from '@/api/account/getApiKeys'; @@ -23,9 +23,9 @@ export default () => { useEffect(() => { getApiKeys() - .then((keys) => setKeys(keys)) + .then(keys => setKeys(keys)) .then(() => setLoading(false)) - .catch((error) => clearAndAddHttpError(error)); + .catch(error => clearAndAddHttpError(error)); }, []); const doDeletion = (identifier: string) => { @@ -33,8 +33,8 @@ export default () => { clearAndAddHttpError(); deleteApiKey(identifier) - .then(() => setKeys((s) => [...(s || []).filter((key) => key.identifier !== identifier)])) - .catch((error) => clearAndAddHttpError(error)) + .then(() => setKeys(s => [...(s || []).filter(key => key.identifier !== identifier)])) + .catch(error => clearAndAddHttpError(error)) .then(() => { setLoading(false); setDeleteIdentifier(''); @@ -46,7 +46,7 @@ export default () => {
- setKeys((s) => [...s!, key])} /> + setKeys(s => [...s!, key])} /> diff --git a/resources/scripts/components/dashboard/AccountOverviewContainer.tsx b/resources/scripts/components/dashboard/AccountOverviewContainer.tsx index 4f64aec48c..2357c48474 100644 --- a/resources/scripts/components/dashboard/AccountOverviewContainer.tsx +++ b/resources/scripts/components/dashboard/AccountOverviewContainer.tsx @@ -1,4 +1,3 @@ -import * as React from 'react'; import ContentBox from '@/components/elements/ContentBox'; import UpdatePasswordForm from '@/components/dashboard/forms/UpdatePasswordForm'; import UpdateEmailAddressForm from '@/components/dashboard/forms/UpdateEmailAddressForm'; @@ -6,7 +5,7 @@ import ConfigureTwoFactorForm from '@/components/dashboard/forms/ConfigureTwoFac import PageContentBlock from '@/components/elements/PageContentBlock'; import tw from 'twin.macro'; import { breakpoint } from '@/theme'; -import styled from 'styled-components/macro'; +import styled from 'styled-components'; import MessageBox from '@/components/MessageBox'; import { useLocation } from 'react-router-dom'; @@ -27,24 +26,26 @@ const Container = styled.div` `; export default () => { - const { state } = useLocation(); + const { state } = useLocation(); return ( - + {state?.twoFactorRedirect && ( - + Your account must have two-factor authentication enabled in order to continue. )} - + - + + - + + diff --git a/resources/scripts/components/dashboard/ApiKeyModal.tsx b/resources/scripts/components/dashboard/ApiKeyModal.tsx index 6d01029e27..f9d5b65a82 100644 --- a/resources/scripts/components/dashboard/ApiKeyModal.tsx +++ b/resources/scripts/components/dashboard/ApiKeyModal.tsx @@ -1,4 +1,4 @@ -import React, { useContext } from 'react'; +import { useContext } from 'react'; import tw from 'twin.macro'; import Button from '@/components/elements/Button'; import asModal from '@/hoc/asModal'; diff --git a/resources/scripts/components/dashboard/DashboardContainer.tsx b/resources/scripts/components/dashboard/DashboardContainer.tsx index 25e0e4d28c..7ca63c9f22 100644 --- a/resources/scripts/components/dashboard/DashboardContainer.tsx +++ b/resources/scripts/components/dashboard/DashboardContainer.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; import { Server } from '@/api/server/getServer'; import getServers from '@/api/getServers'; import ServerRow from '@/components/dashboard/ServerRow'; @@ -20,13 +20,13 @@ export default () => { const [page, setPage] = useState(!isNaN(defaultPage) && defaultPage > 0 ? defaultPage : 1); const { clearFlashes, clearAndAddHttpError } = useFlash(); - const uuid = useStoreState((state) => state.user.data!.uuid); - const rootAdmin = useStoreState((state) => state.user.data!.rootAdmin); + const uuid = useStoreState(state => state.user.data!.uuid); + const rootAdmin = useStoreState(state => state.user.data!.rootAdmin); const [showOnlyAdmin, setShowOnlyAdmin] = usePersistedState(`${uuid}:show_all_servers`, false); const { data: servers, error } = useSWR>( ['/api/client/servers', showOnlyAdmin && rootAdmin, page], - () => getServers({ page, type: showOnlyAdmin && rootAdmin ? 'admin' : undefined }) + () => getServers({ page, type: showOnlyAdmin && rootAdmin ? 'admin' : undefined }), ); useEffect(() => { @@ -58,7 +58,7 @@ export default () => { setShowOnlyAdmin((s) => !s)} + onChange={() => setShowOnlyAdmin(s => !s)} />
)} diff --git a/resources/scripts/components/dashboard/ServerRow.tsx b/resources/scripts/components/dashboard/ServerRow.tsx index 08637bfc2a..08bfc00331 100644 --- a/resources/scripts/components/dashboard/ServerRow.tsx +++ b/resources/scripts/components/dashboard/ServerRow.tsx @@ -1,4 +1,5 @@ -import React, { memo, useEffect, useRef, useState } from 'react'; +import { memo, useEffect, useRef, useState } from 'react'; +import * as React from 'react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faEthernet, faHdd, faMemory, faMicrochip, faServer } from '@fortawesome/free-solid-svg-icons'; import { Link } from 'react-router-dom'; @@ -8,7 +9,7 @@ import { bytesToString, ip, mbToBytes } from '@/lib/formatters'; import tw from 'twin.macro'; import GreyRowBox from '@/components/elements/GreyRowBox'; import Spinner from '@/components/elements/Spinner'; -import styled from 'styled-components/macro'; +import styled from 'styled-components'; import isEqual from 'react-fast-compare'; // Determines if the current value is in an alarm threshold so we can show it in red rather @@ -17,14 +18,14 @@ const isAlarmState = (current: number, limit: number): boolean => limit > 0 && c const Icon = memo( styled(FontAwesomeIcon)<{ $alarm: boolean }>` - ${(props) => (props.$alarm ? tw`text-red-400` : tw`text-neutral-500`)}; + ${props => (props.$alarm ? tw`text-red-400` : tw`text-neutral-500`)}; `, - isEqual + isEqual, ); const IconDescription = styled.p<{ $alarm: boolean }>` ${tw`text-sm ml-2`}; - ${(props) => (props.$alarm ? tw`text-white` : tw`text-neutral-400`)}; + ${props => (props.$alarm ? tw`text-white` : tw`text-neutral-400`)}; `; const StatusIndicatorBox = styled(GreyRowBox)<{ $status: ServerPowerState | undefined }>` @@ -56,8 +57,8 @@ export default ({ server, className }: { server: Server; className?: string }) = const getStats = () => getServerResourceUsage(server.uuid) - .then((data) => setStats(data)) - .catch((error) => console.error(error)); + .then(data => setStats(data)) + .catch(error => console.error(error)); useEffect(() => { setIsSuspended(stats?.isSuspended || server.status === 'suspended'); @@ -106,8 +107,8 @@ export default ({ server, className }: { server: Server; className?: string }) =

{server.allocations - .filter((alloc) => alloc.isDefault) - .map((allocation) => ( + .filter(alloc => alloc.isDefault) + .map(allocation => ( {allocation.alias || ip(allocation.ip)}:{allocation.port} diff --git a/resources/scripts/components/dashboard/activity/ActivityLogContainer.tsx b/resources/scripts/components/dashboard/activity/ActivityLogContainer.tsx index f10701f812..aaf8ccd642 100644 --- a/resources/scripts/components/dashboard/activity/ActivityLogContainer.tsx +++ b/resources/scripts/components/dashboard/activity/ActivityLogContainer.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; import { ActivityLogFilters, useActivityLogs } from '@/api/account/activity'; import { useFlashKey } from '@/plugins/useFlash'; import PageContentBlock from '@/components/elements/PageContentBlock'; @@ -23,7 +23,7 @@ export default () => { }); useEffect(() => { - setFilters((value) => ({ ...value, filters: { ip: hash.ip, event: hash.event } })); + setFilters(value => ({ ...value, filters: { ip: hash.ip, event: hash.event } })); }, [hash]); useEffect(() => { @@ -38,7 +38,7 @@ export default () => { setFilters((value) => ({ ...value, filters: {} }))} + onClick={() => setFilters(value => ({ ...value, filters: {} }))} > Clear Filters @@ -48,7 +48,7 @@ export default () => { ) : (

- {data?.items.map((activity) => ( + {data?.items.map(activity => ( {typeof activity.properties.useragent === 'string' && ( @@ -64,7 +64,7 @@ export default () => { {data && ( setFilters((value) => ({ ...value, page }))} + onPageSelect={page => setFilters(value => ({ ...value, page }))} /> )} diff --git a/resources/scripts/components/dashboard/forms/ConfigureTwoFactorForm.tsx b/resources/scripts/components/dashboard/forms/ConfigureTwoFactorForm.tsx index a59977f5fb..059d1474e4 100644 --- a/resources/scripts/components/dashboard/forms/ConfigureTwoFactorForm.tsx +++ b/resources/scripts/components/dashboard/forms/ConfigureTwoFactorForm.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; import { useStoreState } from 'easy-peasy'; import { ApplicationStore } from '@/state'; import tw from 'twin.macro'; diff --git a/resources/scripts/components/dashboard/forms/CreateApiKeyForm.tsx b/resources/scripts/components/dashboard/forms/CreateApiKeyForm.tsx index 9c273044cb..3e53b02357 100644 --- a/resources/scripts/components/dashboard/forms/CreateApiKeyForm.tsx +++ b/resources/scripts/components/dashboard/forms/CreateApiKeyForm.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import { useState } from 'react'; import { Field, Form, Formik, FormikHelpers } from 'formik'; import { object, string } from 'yup'; import FormikFieldWrapper from '@/components/elements/FormikFieldWrapper'; @@ -11,7 +11,7 @@ import { ApiKey } from '@/api/account/getApiKeys'; import tw from 'twin.macro'; import Button from '@/components/elements/Button'; import Input, { Textarea } from '@/components/elements/Input'; -import styled from 'styled-components/macro'; +import styled from 'styled-components'; import ApiKeyModal from '@/components/dashboard/ApiKeyModal'; interface Values { @@ -36,7 +36,7 @@ export default ({ onKeyCreated }: { onKeyCreated: (key: ApiKey) => void }) => { setApiKey(`${key.identifier}${secretToken}`); onKeyCreated(key); }) - .catch((error) => { + .catch(error => { console.error(error); addError({ key: 'account', message: httpErrorToHuman(error) }); diff --git a/resources/scripts/components/dashboard/forms/DisableTOTPDialog.tsx b/resources/scripts/components/dashboard/forms/DisableTOTPDialog.tsx index 125376eb57..fe5039b794 100644 --- a/resources/scripts/components/dashboard/forms/DisableTOTPDialog.tsx +++ b/resources/scripts/components/dashboard/forms/DisableTOTPDialog.tsx @@ -1,4 +1,5 @@ -import React, { useContext, useEffect, useState } from 'react'; +import { useContext, useEffect, useState } from 'react'; +import * as React from 'react'; import asDialog from '@/hoc/asDialog'; import { Dialog, DialogWrapperContext } from '@/components/elements/dialog'; import { Button } from '@/components/elements/button/index'; @@ -14,10 +15,10 @@ const DisableTOTPDialog = () => { const [password, setPassword] = useState(''); const { clearAndAddHttpError } = useFlashKey('account:two-step'); const { close, setProps } = useContext(DialogWrapperContext); - const updateUserData = useStoreActions((actions) => actions.user.updateUserData); + const updateUserData = useStoreActions(actions => actions.user.updateUserData); useEffect(() => { - setProps((state) => ({ ...state, preventExternalClose: submitting })); + setProps(state => ({ ...state, preventExternalClose: submitting })); }, [submitting]); const submit = (e: React.FormEvent) => { @@ -48,7 +49,7 @@ const DisableTOTPDialog = () => { type={'password'} variant={Input.Text.Variants.Loose} value={password} - onChange={(e) => setPassword(e.currentTarget.value)} + onChange={e => setPassword(e.currentTarget.value)} /> Cancel diff --git a/resources/scripts/components/dashboard/forms/RecoveryTokensDialog.tsx b/resources/scripts/components/dashboard/forms/RecoveryTokensDialog.tsx index 3e2d0ba20b..cddd5d55ab 100644 --- a/resources/scripts/components/dashboard/forms/RecoveryTokensDialog.tsx +++ b/resources/scripts/components/dashboard/forms/RecoveryTokensDialog.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import { Dialog, DialogProps } from '@/components/elements/dialog'; import { Button } from '@/components/elements/button/index'; import CopyOnClick from '@/components/elements/CopyOnClick'; @@ -30,7 +29,7 @@ export default ({ tokens, open, onClose }: RecoveryTokenDialogProps) => {
-                    {grouped.map((value) => (
+                    {grouped.map(value => (
                         
                             {value[0]}
                              
diff --git a/resources/scripts/components/dashboard/forms/SetupTOTPDialog.tsx b/resources/scripts/components/dashboard/forms/SetupTOTPDialog.tsx
index 836b2ef872..f10b1036d2 100644
--- a/resources/scripts/components/dashboard/forms/SetupTOTPDialog.tsx
+++ b/resources/scripts/components/dashboard/forms/SetupTOTPDialog.tsx
@@ -1,4 +1,5 @@
-import React, { useContext, useEffect, useState } from 'react';
+import { useContext, useEffect, useState } from 'react';
+import * as React from 'react';
 import { Dialog, DialogWrapperContext } from '@/components/elements/dialog';
 import getTwoFactorTokenData, { TwoFactorTokenData } from '@/api/account/getTwoFactorTokenData';
 import { useFlashKey } from '@/plugins/useFlash';
@@ -32,11 +33,11 @@ const ConfigureTwoFactorForm = ({ onTokens }: Props) => {
     useEffect(() => {
         getTwoFactorTokenData()
             .then(setToken)
-            .catch((error) => clearAndAddHttpError(error));
+            .catch(error => clearAndAddHttpError(error));
     }, []);
 
     useEffect(() => {
-        setProps((state) => ({ ...state, preventExternalClose: submitting }));
+        setProps(state => ({ ...state, preventExternalClose: submitting }));
     }, [submitting]);
 
     const submit = (e: React.FormEvent) => {
@@ -48,11 +49,11 @@ const ConfigureTwoFactorForm = ({ onTokens }: Props) => {
         setSubmitting(true);
         clearAndAddHttpError();
         enableAccountTwoFactor(value, password)
-            .then((tokens) => {
+            .then(tokens => {
                 updateUserData({ useTotp: true });
                 onTokens(tokens);
             })
-            .catch((error) => {
+            .catch(error => {
                 clearAndAddHttpError(error);
                 setSubmitting(false);
             });
@@ -81,7 +82,7 @@ const ConfigureTwoFactorForm = ({ onTokens }: Props) => {
                 aria-labelledby={'totp-code-description'}
                 variant={Input.Text.Variants.Loose}
                 value={value}
-                onChange={(e) => setValue(e.currentTarget.value)}
+                onChange={e => setValue(e.currentTarget.value)}
                 className={'mt-3'}
                 placeholder={'000000'}
                 type={'text'}
@@ -97,7 +98,7 @@ const ConfigureTwoFactorForm = ({ onTokens }: Props) => {
                 className={'mt-1'}
                 type={'password'}
                 value={password}
-                onChange={(e) => setPassword(e.currentTarget.value)}
+                onChange={e => setPassword(e.currentTarget.value)}
             />
             
                 Cancel
diff --git a/resources/scripts/components/dashboard/forms/UpdateEmailAddressForm.tsx b/resources/scripts/components/dashboard/forms/UpdateEmailAddressForm.tsx
index 462a37f1a1..56632e1f4f 100644
--- a/resources/scripts/components/dashboard/forms/UpdateEmailAddressForm.tsx
+++ b/resources/scripts/components/dashboard/forms/UpdateEmailAddressForm.tsx
@@ -1,4 +1,4 @@
-import React from 'react';
+import { Fragment } from 'react';
 import { Actions, State, useStoreActions, useStoreState } from 'easy-peasy';
 import { Form, Formik, FormikHelpers } from 'formik';
 import * as Yup from 'yup';
@@ -34,15 +34,15 @@ export default () => {
                     type: 'success',
                     key: 'account:email',
                     message: 'Your primary email has been updated.',
-                })
+                }),
             )
-            .catch((error) =>
+            .catch(error =>
                 addFlash({
                     type: 'error',
                     key: 'account:email',
                     title: 'Error',
                     message: httpErrorToHuman(error),
-                })
+                }),
             )
             .then(() => {
                 resetForm();
@@ -53,7 +53,7 @@ export default () => {
     return (
         
             {({ isSubmitting, isValid }) => (
-                
+                
                     
                     
                         
@@ -69,7 +69,7 @@ export default () => {
                             
                         
- + )} ); diff --git a/resources/scripts/components/dashboard/forms/UpdatePasswordForm.tsx b/resources/scripts/components/dashboard/forms/UpdatePasswordForm.tsx index 707ee72d7e..34dcd64e68 100644 --- a/resources/scripts/components/dashboard/forms/UpdatePasswordForm.tsx +++ b/resources/scripts/components/dashboard/forms/UpdatePasswordForm.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import { Fragment } from 'react'; import { Actions, State, useStoreActions, useStoreState } from 'easy-peasy'; import { Form, Formik, FormikHelpers } from 'formik'; import Field from '@/components/elements/Field'; @@ -24,7 +24,7 @@ const schema = Yup.object().shape({ 'Password confirmation does not match the password you entered.', function (value) { return value === this.parent.password; - } + }, ), }); @@ -43,26 +43,26 @@ export default () => { // @ts-expect-error this is valid window.location = '/auth/login'; }) - .catch((error) => + .catch(error => addFlash({ key: 'account:password', type: 'error', title: 'Error', message: httpErrorToHuman(error), - }) + }), ) .then(() => setSubmitting(false)); }; return ( - + {({ isSubmitting, isValid }) => ( - +
{
- + )} - + ); }; diff --git a/resources/scripts/components/dashboard/search/SearchContainer.tsx b/resources/scripts/components/dashboard/search/SearchContainer.tsx index 4da0f3a3f2..a9c0cc03f2 100644 --- a/resources/scripts/components/dashboard/search/SearchContainer.tsx +++ b/resources/scripts/components/dashboard/search/SearchContainer.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import { useState } from 'react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faSearch } from '@fortawesome/free-solid-svg-icons'; import useEventListener from '@/plugins/useEventListener'; @@ -18,7 +18,8 @@ export default () => { return ( <> - {visible && setVisible(false)} />} + setVisible(false)} /> +
setVisible(true)}> diff --git a/resources/scripts/components/dashboard/search/SearchModal.tsx b/resources/scripts/components/dashboard/search/SearchModal.tsx index 1335cff438..dc4de0b3fc 100644 --- a/resources/scripts/components/dashboard/search/SearchModal.tsx +++ b/resources/scripts/components/dashboard/search/SearchModal.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useRef, useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; import Modal, { RequiredModalProps } from '@/components/elements/Modal'; import { Field, Form, Formik, FormikHelpers, useFormikContext } from 'formik'; import { Actions, useStoreActions, useStoreState } from 'easy-peasy'; @@ -10,7 +10,7 @@ import getServers from '@/api/getServers'; import { Server } from '@/api/server/getServer'; import { ApplicationStore } from '@/state'; import { Link } from 'react-router-dom'; -import styled from 'styled-components/macro'; +import styled from 'styled-components'; import tw from 'twin.macro'; import Input from '@/components/elements/Input'; import { ip } from '@/lib/formatters'; @@ -47,10 +47,10 @@ const SearchWatcher = () => { export default ({ ...props }: Props) => { const ref = useRef(null); - const isAdmin = useStoreState((state) => state.user.data!.rootAdmin); + const isAdmin = useStoreState(state => state.user.data!.rootAdmin); const [servers, setServers] = useState([]); const { clearAndAddHttpError, clearFlashes } = useStoreActions( - (actions: Actions) => actions.flashes + (actions: Actions) => actions.flashes, ); const search = debounce(({ term }: Values, { setSubmitting }: FormikHelpers) => { @@ -58,8 +58,8 @@ export default ({ ...props }: Props) => { // if (ref.current) ref.current.focus(); getServers({ query: term, type: isAdmin ? 'admin-all' : undefined }) - .then((servers) => setServers(servers.items.filter((_, index) => index < 5))) - .catch((error) => { + .then(servers => setServers(servers.items.filter((_, index) => index < 5))) + .catch(error => { console.error(error); clearAndAddHttpError({ key: 'search', error }); }) @@ -100,7 +100,7 @@ export default ({ ...props }: Props) => { {servers.length > 0 && (
- {servers.map((server) => ( + {servers.map(server => ( {

{server.name}

{server.allocations - .filter((alloc) => alloc.isDefault) - .map((allocation) => ( + .filter(alloc => alloc.isDefault) + .map(allocation => ( {allocation.alias || ip(allocation.ip)}:{allocation.port} diff --git a/resources/scripts/components/dashboard/ssh/AccountSSHContainer.tsx b/resources/scripts/components/dashboard/ssh/AccountSSHContainer.tsx index 0308b39115..9bbf30f1c7 100644 --- a/resources/scripts/components/dashboard/ssh/AccountSSHContainer.tsx +++ b/resources/scripts/components/dashboard/ssh/AccountSSHContainer.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react'; +import { useEffect } from 'react'; import ContentBox from '@/components/elements/ContentBox'; import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; import FlashMessageRender from '@/components/FlashMessageRender'; diff --git a/resources/scripts/components/dashboard/ssh/CreateSSHKeyForm.tsx b/resources/scripts/components/dashboard/ssh/CreateSSHKeyForm.tsx index 4b4f39cbdf..f4c01c295f 100644 --- a/resources/scripts/components/dashboard/ssh/CreateSSHKeyForm.tsx +++ b/resources/scripts/components/dashboard/ssh/CreateSSHKeyForm.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import { Field, Form, Formik, FormikHelpers } from 'formik'; import { object, string } from 'yup'; import FormikFieldWrapper from '@/components/elements/FormikFieldWrapper'; @@ -6,7 +5,7 @@ import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; import tw from 'twin.macro'; import Button from '@/components/elements/Button'; import Input, { Textarea } from '@/components/elements/Input'; -import styled from 'styled-components/macro'; +import styled from 'styled-components'; import { useFlashKey } from '@/plugins/useFlash'; import { createSSHKey, useSSHKeys } from '@/api/account/ssh-keys'; @@ -27,11 +26,11 @@ export default () => { clearAndAddHttpError(); createSSHKey(values.name, values.publicKey) - .then((key) => { + .then(key => { resetForm(); - mutate((data) => (data || []).concat(key)); + mutate(data => (data || []).concat(key)); }) - .catch((error) => clearAndAddHttpError(error)) + .catch(error => clearAndAddHttpError(error)) .then(() => setSubmitting(false)); }; diff --git a/resources/scripts/components/dashboard/ssh/DeleteSSHKeyButton.tsx b/resources/scripts/components/dashboard/ssh/DeleteSSHKeyButton.tsx index bd9aaf0d55..96994fc5e1 100644 --- a/resources/scripts/components/dashboard/ssh/DeleteSSHKeyButton.tsx +++ b/resources/scripts/components/dashboard/ssh/DeleteSSHKeyButton.tsx @@ -1,7 +1,7 @@ import tw from 'twin.macro'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faTrashAlt } from '@fortawesome/free-solid-svg-icons'; -import React, { useState } from 'react'; +import { useState } from 'react'; import { useFlashKey } from '@/plugins/useFlash'; import { deleteSSHKey, useSSHKeys } from '@/api/account/ssh-keys'; import { Dialog } from '@/components/elements/dialog'; @@ -16,9 +16,9 @@ export default ({ name, fingerprint }: { name: string; fingerprint: string }) => clearAndAddHttpError(); Promise.all([ - mutate((data) => data?.filter((value) => value.fingerprint !== fingerprint), false), + mutate(data => data?.filter(value => value.fingerprint !== fingerprint), false), deleteSSHKey(fingerprint), - ]).catch((error) => { + ]).catch(error => { mutate(undefined, true).catch(console.error); clearAndAddHttpError(error); }); diff --git a/resources/scripts/components/elements/AuthenticatedRoute.tsx b/resources/scripts/components/elements/AuthenticatedRoute.tsx index 2d3b6a6b93..093427d56d 100644 --- a/resources/scripts/components/elements/AuthenticatedRoute.tsx +++ b/resources/scripts/components/elements/AuthenticatedRoute.tsx @@ -1,16 +1,18 @@ -import React from 'react'; -import { Redirect, Route, RouteProps } from 'react-router'; +import type { ReactNode } from 'react'; +import { Navigate, useLocation } from 'react-router-dom'; + import { useStoreState } from '@/state/hooks'; -export default ({ children, ...props }: Omit) => { - const isAuthenticated = useStoreState((state) => !!state.user.data?.uuid); +function AuthenticatedRoute({ children }: { children?: ReactNode }): JSX.Element { + const isAuthenticated = useStoreState(state => !!state.user.data?.uuid); + + const location = useLocation(); + + if (isAuthenticated) { + return <>{children}; + } + + return ; +} - return ( - - isAuthenticated ? children : - } - /> - ); -}; +export default AuthenticatedRoute; diff --git a/resources/scripts/components/elements/Button.tsx b/resources/scripts/components/elements/Button.tsx index e02f00ff15..f25c8b296a 100644 --- a/resources/scripts/components/elements/Button.tsx +++ b/resources/scripts/components/elements/Button.tsx @@ -1,5 +1,5 @@ -import React from 'react'; -import styled, { css } from 'styled-components/macro'; +import * as React from 'react'; +import styled, { css } from 'styled-components'; import tw from 'twin.macro'; import Spinner from '@/components/elements/Spinner'; @@ -13,17 +13,17 @@ interface Props { const ButtonStyle = styled.button>` ${tw`relative inline-block rounded p-2 uppercase tracking-wide text-sm transition-all duration-150 border`}; - ${(props) => + ${props => ((!props.isSecondary && !props.color) || props.color === 'primary') && css` - ${(props) => !props.isSecondary && tw`bg-primary-500 border-primary-600 border text-primary-50`}; + ${props => !props.isSecondary && tw`bg-primary-500 border-primary-600 border text-primary-50`}; &:hover:not(:disabled) { ${tw`bg-primary-600 border-primary-700`}; } `}; - ${(props) => + ${props => props.color === 'grey' && css` ${tw`border-neutral-600 bg-neutral-500 text-neutral-50`}; @@ -33,7 +33,7 @@ const ButtonStyle = styled.button>` } `}; - ${(props) => + ${props => props.color === 'green' && css` ${tw`border-green-600 bg-green-500 text-green-50`}; @@ -42,7 +42,7 @@ const ButtonStyle = styled.button>` ${tw`bg-green-600 border-green-700`}; } - ${(props) => + ${props => props.isSecondary && css` &:active:not(:disabled) { @@ -51,7 +51,7 @@ const ButtonStyle = styled.button>` `}; `}; - ${(props) => + ${props => props.color === 'red' && css` ${tw`border-red-600 bg-red-500 text-red-50`}; @@ -60,7 +60,7 @@ const ButtonStyle = styled.button>` ${tw`bg-red-600 border-red-700`}; } - ${(props) => + ${props => props.isSecondary && css` &:active:not(:disabled) { @@ -69,21 +69,21 @@ const ButtonStyle = styled.button>` `}; `}; - ${(props) => props.size === 'xsmall' && tw`px-2 py-1 text-xs`}; - ${(props) => (!props.size || props.size === 'small') && tw`px-4 py-2`}; - ${(props) => props.size === 'large' && tw`p-4 text-sm`}; - ${(props) => props.size === 'xlarge' && tw`p-4 w-full`}; + ${props => props.size === 'xsmall' && tw`px-2 py-1 text-xs`}; + ${props => (!props.size || props.size === 'small') && tw`px-4 py-2`}; + ${props => props.size === 'large' && tw`p-4 text-sm`}; + ${props => props.size === 'xlarge' && tw`p-4 w-full`}; - ${(props) => + ${props => props.isSecondary && css` ${tw`border-neutral-600 bg-transparent text-neutral-200`}; &:hover:not(:disabled) { ${tw`border-neutral-500 text-neutral-100`}; - ${(props) => props.color === 'red' && tw`bg-red-500 border-red-600 text-red-50`}; - ${(props) => props.color === 'primary' && tw`bg-primary-500 border-primary-600 text-primary-50`}; - ${(props) => props.color === 'green' && tw`bg-green-500 border-green-600 text-green-50`}; + ${props => props.color === 'red' && tw`bg-red-500 border-red-600 text-red-50`}; + ${props => props.color === 'primary' && tw`bg-primary-500 border-primary-600 text-primary-50`}; + ${props => props.color === 'green' && tw`bg-green-500 border-green-600 text-green-50`}; } `}; @@ -108,7 +108,7 @@ const Button: React.FC = ({ children, isLoading, ...props }) => type LinkProps = Omit & Props; -const LinkButton: React.FC = (props) => ; +const LinkButton: React.FC = props => ; export { LinkButton, ButtonStyle }; export default Button; diff --git a/resources/scripts/components/elements/Can.tsx b/resources/scripts/components/elements/Can.tsx index 8240519368..70b3fa8476 100644 --- a/resources/scripts/components/elements/Can.tsx +++ b/resources/scripts/components/elements/Can.tsx @@ -1,24 +1,25 @@ -import React, { memo } from 'react'; -import { usePermissions } from '@/plugins/usePermissions'; +import type { ReactNode } from 'react'; +import { memo } from 'react'; import isEqual from 'react-fast-compare'; +import { usePermissions } from '@/plugins/usePermissions'; interface Props { action: string | string[]; matchAny?: boolean; - renderOnError?: React.ReactNode | null; - children: React.ReactNode; + renderOnError?: ReactNode | null; + children: ReactNode; } -const Can = ({ action, matchAny = false, renderOnError, children }: Props) => { +function Can({ action, matchAny = false, renderOnError, children }: Props) { const can = usePermissions(action); return ( <> - {(matchAny && can.filter((p) => p).length > 0) || (!matchAny && can.every((p) => p)) - ? children - : renderOnError} + {(matchAny && can.filter(p => p).length > 0) || (!matchAny && can.every(p => p)) ? children : renderOnError} ); -}; +} + +const MemoizedCan = memo(Can, isEqual); -export default memo(Can, isEqual); +export default MemoizedCan; diff --git a/resources/scripts/components/elements/Checkbox.tsx b/resources/scripts/components/elements/Checkbox.tsx index 731fd24de5..1432fe1e39 100644 --- a/resources/scripts/components/elements/Checkbox.tsx +++ b/resources/scripts/components/elements/Checkbox.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import { Field, FieldProps } from 'formik'; import Input from '@/components/elements/Input'; @@ -29,7 +28,7 @@ const Checkbox = ({ name, value, className, ...props }: Props & InputProps) => ( type={'checkbox'} checked={(field.value || []).includes(value)} onClick={() => form.setFieldTouched(field.name, true)} - onChange={(e) => { + onChange={e => { const set = new Set(field.value); set.has(value) ? set.delete(value) : set.add(value); diff --git a/resources/scripts/components/elements/Code.tsx b/resources/scripts/components/elements/Code.tsx index 30eac0d861..02640d699e 100644 --- a/resources/scripts/components/elements/Code.tsx +++ b/resources/scripts/components/elements/Code.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import * as React from 'react'; import classNames from 'classnames'; interface CodeProps { diff --git a/resources/scripts/components/elements/CodemirrorEditor.tsx b/resources/scripts/components/elements/CodemirrorEditor.tsx index 99a7b5e852..48948ad74b 100644 --- a/resources/scripts/components/elements/CodemirrorEditor.tsx +++ b/resources/scripts/components/elements/CodemirrorEditor.tsx @@ -1,7 +1,9 @@ -import React, { useCallback, useEffect, useState } from 'react'; import CodeMirror from 'codemirror'; -import styled from 'styled-components/macro'; +import type { CSSProperties } from 'react'; +import { useCallback, useEffect, useState } from 'react'; +import styled from 'styled-components'; import tw from 'twin.macro'; + import modes from '@/modes'; require('codemirror/lib/codemirror.css'); @@ -106,7 +108,7 @@ const EditorContainer = styled.div` `; export interface Props { - style?: React.CSSProperties; + style?: CSSProperties; initialContent?: string; mode: string; filename?: string; @@ -119,7 +121,7 @@ const findModeByFilename = (filename: string) => { for (let i = 0; i < modes.length; i++) { const info = modes[i]; - if (info.file && info.file.test(filename)) { + if (info?.file !== undefined && info.file.test(filename)) { return info; } } @@ -130,7 +132,7 @@ const findModeByFilename = (filename: string) => { if (ext) { for (let i = 0; i < modes.length; i++) { const info = modes[i]; - if (info.ext) { + if (info?.ext !== undefined) { for (let j = 0; j < info.ext.length; j++) { if (info.ext[j] === ext) { return info; @@ -146,10 +148,12 @@ const findModeByFilename = (filename: string) => { export default ({ style, initialContent, filename, mode, fetchContent, onContentSaved, onModeChanged }: Props) => { const [editor, setEditor] = useState(); - const ref = useCallback((node) => { - if (!node) return; + const ref = useCallback<(_?: unknown) => void>(node => { + if (node === undefined) { + return; + } - const e = CodeMirror.fromTextArea(node, { + const e = CodeMirror.fromTextArea(node as HTMLTextAreaElement, { mode: 'text/plain', theme: 'ayu-mirage', indentUnit: 4, @@ -158,7 +162,6 @@ export default ({ style, initialContent, filename, mode, fetchContent, onContent indentWithTabs: false, lineWrapping: true, lineNumbers: true, - foldGutter: true, fixedGutter: true, scrollbarStyle: 'overlay', coverGutterNextToScrollbar: false, diff --git a/resources/scripts/components/elements/ConfirmationModal.tsx b/resources/scripts/components/elements/ConfirmationModal.tsx index 52f9f9e3cc..ffb85a13f8 100644 --- a/resources/scripts/components/elements/ConfirmationModal.tsx +++ b/resources/scripts/components/elements/ConfirmationModal.tsx @@ -1,23 +1,28 @@ -import React, { useContext } from 'react'; +import type { ReactNode } from 'react'; +import { useContext } from 'react'; import tw from 'twin.macro'; + import Button from '@/components/elements/Button'; -import asModal from '@/hoc/asModal'; import ModalContext from '@/context/ModalContext'; +import asModal from '@/hoc/asModal'; + +interface Props { + children: ReactNode; -type Props = { title: string; buttonText: string; onConfirmed: () => void; showSpinnerOverlay?: boolean; -}; +} -const ConfirmationModal: React.FC = ({ title, children, buttonText, onConfirmed }) => { +function ConfirmationModal({ title, children, buttonText, onConfirmed }: Props) { const { dismiss } = useContext(ModalContext); return ( <>

{title}

{children}
+
); -}; - -ConfirmationModal.displayName = 'ConfirmationModal'; +} -export default asModal((props) => ({ +export default asModal(props => ({ showSpinnerOverlay: props.showSpinnerOverlay, }))(ConfirmationModal); diff --git a/resources/scripts/components/elements/ContentBox.tsx b/resources/scripts/components/elements/ContentBox.tsx index b829088c83..fb31f9e946 100644 --- a/resources/scripts/components/elements/ContentBox.tsx +++ b/resources/scripts/components/elements/ContentBox.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import * as React from 'react'; import FlashMessageRender from '@/components/FlashMessageRender'; import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; import tw from 'twin.macro'; diff --git a/resources/scripts/components/elements/ContentContainer.tsx b/resources/scripts/components/elements/ContentContainer.tsx index 799f512d22..0ce40c416b 100644 --- a/resources/scripts/components/elements/ContentContainer.tsx +++ b/resources/scripts/components/elements/ContentContainer.tsx @@ -1,4 +1,4 @@ -import styled from 'styled-components/macro'; +import styled from 'styled-components'; import { breakpoint } from '@/theme'; import tw from 'twin.macro'; diff --git a/resources/scripts/components/elements/CopyOnClick.tsx b/resources/scripts/components/elements/CopyOnClick.tsx index 431e9c8d6c..80c01277c6 100644 --- a/resources/scripts/components/elements/CopyOnClick.tsx +++ b/resources/scripts/components/elements/CopyOnClick.tsx @@ -1,13 +1,15 @@ -import React, { useEffect, useState } from 'react'; -import Fade from '@/components/elements/Fade'; -import Portal from '@/components/elements/Portal'; -import copy from 'copy-to-clipboard'; import classNames from 'classnames'; +import copy from 'copy-to-clipboard'; +import type { MouseEvent, ReactNode } from 'react'; +import { Children, cloneElement, isValidElement, useEffect, useState } from 'react'; + +import Portal from '@/components/elements/Portal'; +import FadeTransition from '@/components/elements/transitions/FadeTransition'; interface CopyOnClickProps { text: string | number | null | undefined; showInNotification?: boolean; - children: React.ReactNode; + children: ReactNode; } const CopyOnClick = ({ text, showInNotification = true, children }: CopyOnClickProps) => { @@ -25,15 +27,16 @@ const CopyOnClick = ({ text, showInNotification = true, children }: CopyOnClickP }; }, [copied]); - if (!React.isValidElement(children)) { + if (!isValidElement(children)) { throw new Error('Component passed to must be a valid React element.'); } const child = !text - ? React.Children.only(children) - : React.cloneElement(React.Children.only(children), { + ? Children.only(children) + : cloneElement(Children.only(children), { + // @ts-expect-error I don't know className: classNames(children.props.className || '', 'cursor-pointer'), - onClick: (e: React.MouseEvent) => { + onClick: (e: MouseEvent) => { copy(String(text)); setCopied(true); if (typeof children.props.onClick === 'function') { @@ -46,9 +49,9 @@ const CopyOnClick = ({ text, showInNotification = true, children }: CopyOnClickP <> {copied && ( - -
-
+ +
+

{showInNotification ? `Copied "${String(text)}" to clipboard.` @@ -56,7 +59,7 @@ const CopyOnClick = ({ text, showInNotification = true, children }: CopyOnClickP

- +
)} {child} diff --git a/resources/scripts/components/elements/DropdownMenu.tsx b/resources/scripts/components/elements/DropdownMenu.tsx index 0c5361a8c8..ea8208369f 100644 --- a/resources/scripts/components/elements/DropdownMenu.tsx +++ b/resources/scripts/components/elements/DropdownMenu.tsx @@ -1,11 +1,13 @@ -import React, { createRef } from 'react'; -import styled from 'styled-components/macro'; +import type { MouseEvent as ReactMouseEvent, ReactNode } from 'react'; +import { createRef, PureComponent } from 'react'; +import styled from 'styled-components'; import tw from 'twin.macro'; -import Fade from '@/components/elements/Fade'; + +import FadeTransition from '@/components/elements/transitions/FadeTransition'; interface Props { - children: React.ReactNode; - renderToggle: (onClick: (e: React.MouseEvent) => void) => React.ReactChild; + children: ReactNode; + renderToggle: (onClick: (e: ReactMouseEvent) => void) => any; } export const DropdownButtonRow = styled.button<{ danger?: boolean }>` @@ -13,7 +15,7 @@ export const DropdownButtonRow = styled.button<{ danger?: boolean }>` transition: 150ms all ease; &:hover { - ${(props) => (props.danger ? tw`text-red-700 bg-red-100` : tw`text-neutral-700 bg-neutral-100`)}; + ${props => (props.danger ? tw`text-red-700 bg-red-100` : tw`text-neutral-700 bg-neutral-100`)}; } `; @@ -22,19 +24,19 @@ interface State { visible: boolean; } -class DropdownMenu extends React.PureComponent { +class DropdownMenu extends PureComponent { menu = createRef(); - state: State = { + override state: State = { posX: 0, visible: false, }; - componentWillUnmount() { + override componentWillUnmount() { this.removeListeners(); } - componentDidUpdate(prevProps: Readonly, prevState: Readonly) { + override componentDidUpdate(_prevProps: Readonly, prevState: Readonly) { const menu = this.menu.current; if (this.state.visible && !prevState.visible && menu) { @@ -48,19 +50,21 @@ class DropdownMenu extends React.PureComponent { } } - removeListeners = () => { + removeListeners() { document.removeEventListener('click', this.windowListener); document.removeEventListener('contextmenu', this.contextMenuListener); - }; + } - onClickHandler = (e: React.MouseEvent) => { + onClickHandler(e: ReactMouseEvent) { e.preventDefault(); this.triggerMenu(e.clientX); - }; + } - contextMenuListener = () => this.setState({ visible: false }); + contextMenuListener() { + this.setState({ visible: false }); + } - windowListener = (e: MouseEvent) => { + windowListener(e: MouseEvent): any { const menu = this.menu.current; if (e.button === 2 || !this.state.visible || !menu) { @@ -74,22 +78,24 @@ class DropdownMenu extends React.PureComponent { if (e.target !== menu && !menu.contains(e.target as Node)) { this.setState({ visible: false }); } - }; + } - triggerMenu = (posX: number) => - this.setState((s) => ({ + triggerMenu(posX: number) { + this.setState(s => ({ posX: !s.visible ? posX : s.posX, visible: !s.visible, })); + } - render() { + override render() { return (
{this.props.renderToggle(this.onClickHandler)} - + +
{ + onClick={e => { e.stopPropagation(); this.setState({ visible: false }); }} @@ -98,7 +104,7 @@ class DropdownMenu extends React.PureComponent { > {this.props.children}
-
+
); } diff --git a/resources/scripts/components/elements/ErrorBoundary.tsx b/resources/scripts/components/elements/ErrorBoundary.tsx index c92d952b52..95819a02f5 100644 --- a/resources/scripts/components/elements/ErrorBoundary.tsx +++ b/resources/scripts/components/elements/ErrorBoundary.tsx @@ -1,15 +1,20 @@ -import React from 'react'; +import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons'; +import type { ReactNode } from 'react'; +import { Component } from 'react'; import tw from 'twin.macro'; + import Icon from '@/components/elements/Icon'; -import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons'; + +interface Props { + children?: ReactNode; +} interface State { hasError: boolean; } -// eslint-disable-next-line @typescript-eslint/ban-types -class ErrorBoundary extends React.Component<{}, State> { - state: State = { +class ErrorBoundary extends Component { + override state: State = { hasError: false, }; @@ -17,15 +22,16 @@ class ErrorBoundary extends React.Component<{}, State> { return { hasError: true }; } - componentDidCatch(error: Error) { + override componentDidCatch(error: Error) { console.error(error); } - render() { + override render() { return this.state.hasError ? (
+

An error was encountered by the application while rendering this view. Try refreshing the page.

diff --git a/resources/scripts/components/elements/Fade.tsx b/resources/scripts/components/elements/Fade.tsx deleted file mode 100644 index 41bcf0be75..0000000000 --- a/resources/scripts/components/elements/Fade.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import React from 'react'; -import tw from 'twin.macro'; -import styled from 'styled-components/macro'; -import CSSTransition, { CSSTransitionProps } from 'react-transition-group/CSSTransition'; - -interface Props extends Omit { - timeout: number; -} - -const Container = styled.div<{ timeout: number }>` - .fade-enter, - .fade-exit, - .fade-appear { - will-change: opacity; - } - - .fade-enter, - .fade-appear { - ${tw`opacity-0`}; - - &.fade-enter-active, - &.fade-appear-active { - ${tw`opacity-100 transition-opacity ease-in`}; - transition-duration: ${(props) => props.timeout}ms; - } - } - - .fade-exit { - ${tw`opacity-100`}; - - &.fade-exit-active { - ${tw`opacity-0 transition-opacity ease-in`}; - transition-duration: ${(props) => props.timeout}ms; - } - } -`; - -const Fade: React.FC = ({ timeout, children, ...props }) => ( - - - {children} - - -); -Fade.displayName = 'Fade'; - -export default Fade; diff --git a/resources/scripts/components/elements/Field.tsx b/resources/scripts/components/elements/Field.tsx index 6ed5023c36..3ce78349a4 100644 --- a/resources/scripts/components/elements/Field.tsx +++ b/resources/scripts/components/elements/Field.tsx @@ -1,4 +1,5 @@ -import React, { forwardRef } from 'react'; +import { forwardRef } from 'react'; +import * as React from 'react'; import { Field as FormikField, FieldProps } from 'formik'; import Input from '@/components/elements/Input'; import Label from '@/components/elements/Label'; @@ -41,7 +42,7 @@ const Field = forwardRef(
)} - ) + ), ); Field.displayName = 'Field'; diff --git a/resources/scripts/components/elements/FormikFieldWrapper.tsx b/resources/scripts/components/elements/FormikFieldWrapper.tsx index 95a645a85d..91db2ff430 100644 --- a/resources/scripts/components/elements/FormikFieldWrapper.tsx +++ b/resources/scripts/components/elements/FormikFieldWrapper.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import * as React from 'react'; import { Field, FieldProps } from 'formik'; import InputError from '@/components/elements/InputError'; import Label from '@/components/elements/Label'; diff --git a/resources/scripts/components/elements/FormikSwitch.tsx b/resources/scripts/components/elements/FormikSwitch.tsx index 4736e0794f..52faf056b4 100644 --- a/resources/scripts/components/elements/FormikSwitch.tsx +++ b/resources/scripts/components/elements/FormikSwitch.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import FormikFieldWrapper from '@/components/elements/FormikFieldWrapper'; import { Field, FieldProps } from 'formik'; import Switch, { SwitchProps } from '@/components/elements/Switch'; diff --git a/resources/scripts/components/elements/GreyRowBox.tsx b/resources/scripts/components/elements/GreyRowBox.tsx index e5a7f96859..99e3fadf98 100644 --- a/resources/scripts/components/elements/GreyRowBox.tsx +++ b/resources/scripts/components/elements/GreyRowBox.tsx @@ -1,10 +1,10 @@ -import styled from 'styled-components/macro'; +import styled from 'styled-components'; import tw from 'twin.macro'; export default styled.div<{ $hoverable?: boolean }>` ${tw`flex rounded no-underline text-neutral-200 items-center bg-neutral-700 p-4 border border-transparent transition-colors duration-150 overflow-hidden`}; - ${(props) => props.$hoverable !== false && tw`hover:border-neutral-500`}; + ${props => props.$hoverable !== false && tw`hover:border-neutral-500`}; & .icon { ${tw`rounded-full w-16 flex items-center justify-center bg-neutral-500 p-3`}; diff --git a/resources/scripts/components/elements/Icon.tsx b/resources/scripts/components/elements/Icon.tsx index 465d5a52d3..d80ca96c08 100644 --- a/resources/scripts/components/elements/Icon.tsx +++ b/resources/scripts/components/elements/Icon.tsx @@ -1,4 +1,4 @@ -import React, { CSSProperties } from 'react'; +import { CSSProperties } from 'react'; import { IconDefinition } from '@fortawesome/fontawesome-svg-core'; import tw from 'twin.macro'; diff --git a/resources/scripts/components/elements/Input.tsx b/resources/scripts/components/elements/Input.tsx index 677a5014d8..c9283e0552 100644 --- a/resources/scripts/components/elements/Input.tsx +++ b/resources/scripts/components/elements/Input.tsx @@ -1,4 +1,4 @@ -import styled, { css } from 'styled-components/macro'; +import styled, { css } from 'styled-components'; import tw from 'twin.macro'; export interface Props { @@ -45,7 +45,7 @@ const inputStyle = css` & + .input-help { ${tw`mt-1 text-xs`}; - ${(props) => (props.hasError ? tw`text-red-200` : tw`text-neutral-200`)}; + ${props => (props.hasError ? tw`text-red-200` : tw`text-neutral-200`)}; } &:required, @@ -55,15 +55,15 @@ const inputStyle = css` &:not(:disabled):not(:read-only):focus { ${tw`shadow-md border-primary-300 ring-2 ring-primary-400 ring-opacity-50`}; - ${(props) => props.hasError && tw`border-red-300 ring-red-200`}; + ${props => props.hasError && tw`border-red-300 ring-red-200`}; } &:disabled { ${tw`opacity-75`}; } - ${(props) => props.isLight && light}; - ${(props) => props.hasError && tw`text-red-100 border-red-400 hover:border-red-300`}; + ${props => props.isLight && light}; + ${props => props.hasError && tw`text-red-100 border-red-400 hover:border-red-300`}; `; const Input = styled.input` diff --git a/resources/scripts/components/elements/InputError.tsx b/resources/scripts/components/elements/InputError.tsx index dfabf72d9f..d39735a3a3 100644 --- a/resources/scripts/components/elements/InputError.tsx +++ b/resources/scripts/components/elements/InputError.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import { FormikErrors, FormikTouched } from 'formik'; import tw from 'twin.macro'; import { capitalize } from '@/lib/strings'; @@ -15,7 +14,7 @@ const InputError = ({ errors, touched, name, children }: Props) =>

{typeof errors[name] === 'string' ? capitalize(errors[name] as string) - : capitalize((errors[name] as unknown as string[])[0])} + : capitalize((errors[name] as unknown as string[])[0] ?? '')}

) : ( <>{children ?

{children}

: null} diff --git a/resources/scripts/components/elements/InputSpinner.tsx b/resources/scripts/components/elements/InputSpinner.tsx index 2ed77b433b..583214a02d 100644 --- a/resources/scripts/components/elements/InputSpinner.tsx +++ b/resources/scripts/components/elements/InputSpinner.tsx @@ -1,14 +1,15 @@ -import React from 'react'; -import Spinner from '@/components/elements/Spinner'; -import Fade from '@/components/elements/Fade'; +import type { ReactNode } from 'react'; +import styled, { css } from 'styled-components'; import tw from 'twin.macro'; -import styled, { css } from 'styled-components/macro'; + import Select from '@/components/elements/Select'; +import Spinner from '@/components/elements/Spinner'; +import FadeTransition from '@/components/elements/transitions/FadeTransition'; const Container = styled.div<{ visible?: boolean }>` ${tw`relative`}; - ${(props) => + ${props => props.visible && css` & ${Select} { @@ -17,15 +18,18 @@ const Container = styled.div<{ visible?: boolean }>` `}; `; -const InputSpinner = ({ visible, children }: { visible: boolean; children: React.ReactNode }) => ( - - -
- -
-
- {children} -
-); +function InputSpinner({ visible, children }: { visible: boolean; children: ReactNode }) { + return ( + + +
+ +
+
+ + {children} +
+ ); +} export default InputSpinner; diff --git a/resources/scripts/components/elements/Label.tsx b/resources/scripts/components/elements/Label.tsx index 704e8324b5..c97e99c651 100644 --- a/resources/scripts/components/elements/Label.tsx +++ b/resources/scripts/components/elements/Label.tsx @@ -1,9 +1,9 @@ -import styled from 'styled-components/macro'; +import styled from 'styled-components'; import tw from 'twin.macro'; const Label = styled.label<{ isLight?: boolean }>` ${tw`block text-xs uppercase text-neutral-200 mb-1 sm:mb-2`}; - ${(props) => props.isLight && tw`text-neutral-700`}; + ${props => props.isLight && tw`text-neutral-700`}; `; export default Label; diff --git a/resources/scripts/components/elements/Modal.tsx b/resources/scripts/components/elements/Modal.tsx index d4f6dfece0..89de265625 100644 --- a/resources/scripts/components/elements/Modal.tsx +++ b/resources/scripts/components/elements/Modal.tsx @@ -1,12 +1,16 @@ -import React, { useEffect, useMemo, useRef, useState } from 'react'; -import Spinner from '@/components/elements/Spinner'; +import type { ReactNode } from 'react'; +import { Fragment, useEffect, useMemo, useRef, useState } from 'react'; +import { createPortal } from 'react-dom'; +import styled, { css } from 'styled-components'; import tw from 'twin.macro'; -import styled, { css } from 'styled-components/macro'; + +import Spinner from '@/components/elements/Spinner'; import { breakpoint } from '@/theme'; -import Fade from '@/components/elements/Fade'; -import { createPortal } from 'react-dom'; +import FadeTransition from '@/components/elements/transitions/FadeTransition'; export interface RequiredModalProps { + children?: ReactNode; + visible: boolean; onDismissed: () => void; appear?: boolean; @@ -32,7 +36,7 @@ const ModalContainer = styled.div<{ alignTop?: boolean }>` ${breakpoint('lg')`max-width: 50%`}; ${tw`relative flex flex-col w-full m-auto`}; - ${(props) => + ${props => props.alignTop && css` margin-top: 20%; @@ -55,7 +59,7 @@ const ModalContainer = styled.div<{ alignTop?: boolean }>` } `; -const Modal: React.FC = ({ +function Modal({ visible, appear, dismissable, @@ -65,7 +69,7 @@ const Modal: React.FC = ({ closeOnEscape = true, onDismissed, children, -}) => { +}: ModalProps) { const [render, setRender] = useState(visible); const isDismissable = useMemo(() => { @@ -85,14 +89,20 @@ const Modal: React.FC = ({ }; }, [isDismissable, closeOnEscape, render]); - useEffect(() => setRender(visible), [visible]); + useEffect(() => { + setRender(visible); + + if (!visible) { + onDismissed(); + } + }, [visible]); return ( - onDismissed()}> + e.stopPropagation()} - onContextMenu={(e) => e.stopPropagation()} - onMouseDown={(e) => { + onClick={e => e.stopPropagation()} + onContextMenu={e => e.stopPropagation()} + onMouseDown={e => { if (isDismissable && closeOnBackground) { e.stopPropagation(); if (e.target === e.currentTarget) { @@ -119,16 +129,16 @@ const Modal: React.FC = ({
)} - {showSpinnerOverlay && ( - -
- -
-
- )} + + +
+ +
+
+
@@ -136,14 +146,14 @@ const Modal: React.FC = ({
- + ); -}; +} -const PortaledModal: React.FC = ({ children, ...props }) => { +function PortaledModal({ children, ...props }: ModalProps): JSX.Element { const element = useRef(document.getElementById('modal-portal')); return createPortal({children}, element.current!); -}; +} export default PortaledModal; diff --git a/resources/scripts/components/elements/PageContentBlock.tsx b/resources/scripts/components/elements/PageContentBlock.tsx index a4df19567f..9def94e52f 100644 --- a/resources/scripts/components/elements/PageContentBlock.tsx +++ b/resources/scripts/components/elements/PageContentBlock.tsx @@ -1,16 +1,19 @@ -import React, { useEffect } from 'react'; -import ContentContainer from '@/components/elements/ContentContainer'; -import { CSSTransition } from 'react-transition-group'; +import type { ReactNode } from 'react'; +import { useEffect } from 'react'; import tw from 'twin.macro'; + +import ContentContainer from '@/components/elements/ContentContainer'; import FlashMessageRender from '@/components/FlashMessageRender'; export interface PageContentBlockProps { + children?: ReactNode; + title?: string; className?: string; showFlashKey?: string; } -const PageContentBlock: React.FC = ({ title, showFlashKey, className, children }) => { +function PageContentBlock({ title, showFlashKey, className, children }: PageContentBlockProps) { useEffect(() => { if (title) { document.title = title; @@ -18,28 +21,27 @@ const PageContentBlock: React.FC = ({ title, showFlashKey }, [title]); return ( - - <> - - {showFlashKey && } - {children} - - -

- - Pterodactyl® - -  © 2015 - {new Date().getFullYear()} -

-
- -
+ <> + + {showFlashKey && } + {children} + + + +

+ + Pterodactyl® + +  © 2015 - {new Date().getFullYear()} +

+
+ ); -}; +} export default PageContentBlock; diff --git a/resources/scripts/components/elements/Pagination.tsx b/resources/scripts/components/elements/Pagination.tsx index 7a2951f778..0bb395ea85 100644 --- a/resources/scripts/components/elements/Pagination.tsx +++ b/resources/scripts/components/elements/Pagination.tsx @@ -1,7 +1,7 @@ -import React from 'react'; +import * as React from 'react'; import { PaginatedResult } from '@/api/http'; import tw from 'twin.macro'; -import styled from 'styled-components/macro'; +import styled from 'styled-components'; import Button from '@/components/elements/Button'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faAngleDoubleLeft, faAngleDoubleRight } from '@fortawesome/free-solid-svg-icons'; @@ -48,12 +48,12 @@ function Pagination({ data: { items, pagination }, onPageSelect, children }: {children({ items, isFirstPage, isLastPage })} {pages.length > 1 && (
- {pages[0] > 1 && !isFirstPage && ( + {(pages?.[0] ?? 0) > 1 && !isFirstPage && ( onPageSelect(1)}> )} - {pages.map((i) => ( + {pages.map(i => ( ({ data: { items, pagination }, onPageSelect, children }: {i} ))} - {pages[4] < pagination.totalPages && !isLastPage && ( + {(pages?.[4] ?? 0) < pagination.totalPages && !isLastPage && ( onPageSelect(pagination.totalPages)}> diff --git a/resources/scripts/components/elements/PermissionRoute.tsx b/resources/scripts/components/elements/PermissionRoute.tsx index e7a46e5705..ed50db8521 100644 --- a/resources/scripts/components/elements/PermissionRoute.tsx +++ b/resources/scripts/components/elements/PermissionRoute.tsx @@ -1,28 +1,26 @@ -import React from 'react'; -import { Route } from 'react-router-dom'; -import { RouteProps } from 'react-router'; -import Can from '@/components/elements/Can'; +import type { ReactNode } from 'react'; + import { ServerError } from '@/components/elements/ScreenBlock'; +import { usePermissions } from '@/plugins/usePermissions'; + +interface Props { + children?: ReactNode; + + permission?: string | string[]; +} + +function PermissionRoute({ children, permission }: Props): JSX.Element { + if (permission === undefined) { + return <>{children}; + } + + const can = usePermissions(permission); + + if (can.filter(p => p).length > 0) { + return <>{children}; + } -interface Props extends Omit { - path: string; - permission: string | string[] | null; + return ; } -export default ({ permission, children, ...props }: Props) => ( - - {!permission ? ( - children - ) : ( - - } - > - {children} - - )} - -); +export default PermissionRoute; diff --git a/resources/scripts/components/elements/Portal.tsx b/resources/scripts/components/elements/Portal.tsx index e0be757786..1bbc03e4c3 100644 --- a/resources/scripts/components/elements/Portal.tsx +++ b/resources/scripts/components/elements/Portal.tsx @@ -1,4 +1,5 @@ -import React, { useRef } from 'react'; +import { useRef } from 'react'; +import * as React from 'react'; import { createPortal } from 'react-dom'; export default ({ children }: { children: React.ReactNode }) => { diff --git a/resources/scripts/components/elements/ProgressBar.tsx b/resources/scripts/components/elements/ProgressBar.tsx index c94ae80f3d..9ad271972c 100644 --- a/resources/scripts/components/elements/ProgressBar.tsx +++ b/resources/scripts/components/elements/ProgressBar.tsx @@ -1,25 +1,19 @@ -import React, { useEffect, useRef, useState } from 'react'; -import styled from 'styled-components/macro'; +import { Transition } from '@headlessui/react'; import { useStoreActions, useStoreState } from 'easy-peasy'; -import { randomInt } from '@/helpers'; -import { CSSTransition } from 'react-transition-group'; -import tw from 'twin.macro'; +import { Fragment, useEffect, useRef, useState } from 'react'; -const BarFill = styled.div` - ${tw`h-full bg-cyan-400`}; - transition: 250ms ease-in-out; - box-shadow: 0 -2px 10px 2px hsl(178, 78%, 57%); -`; +import { randomInt } from '@/helpers'; type Timer = ReturnType; -export default () => { - const interval = useRef(null) as React.MutableRefObject; - const timeout = useRef(null) as React.MutableRefObject; +function ProgressBar() { + const interval = useRef(); + const timeout = useRef(); const [visible, setVisible] = useState(false); - const progress = useStoreState((state) => state.progress.progress); - const continuous = useStoreState((state) => state.progress.continuous); - const setProgress = useStoreActions((actions) => actions.progress.setProgress); + + const continuous = useStoreState(state => state.progress.continuous); + const progress = useStoreState(state => state.progress.progress); + const setProgress = useStoreActions(actions => actions.progress.setProgress); useEffect(() => { return () => { @@ -59,10 +53,26 @@ export default () => { }, [progress, continuous]); return ( -
- - - +
+ +
+
); -}; +} + +export default ProgressBar; diff --git a/resources/scripts/components/elements/ScreenBlock.tsx b/resources/scripts/components/elements/ScreenBlock.tsx index 0b57e92c6d..26e8ec5ac7 100644 --- a/resources/scripts/components/elements/ScreenBlock.tsx +++ b/resources/scripts/components/elements/ScreenBlock.tsx @@ -1,8 +1,7 @@ -import React from 'react'; import PageContentBlock from '@/components/elements/PageContentBlock'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faArrowLeft, faSyncAlt } from '@fortawesome/free-solid-svg-icons'; -import styled, { keyframes } from 'styled-components/macro'; +import styled, { keyframes } from 'styled-components'; import tw from 'twin.macro'; import Button from '@/components/elements/Button'; import NotFoundSvg from '@/assets/images/not_found.svg'; diff --git a/resources/scripts/components/elements/Select.tsx b/resources/scripts/components/elements/Select.tsx index fc50f252d4..de74a8aef0 100644 --- a/resources/scripts/components/elements/Select.tsx +++ b/resources/scripts/components/elements/Select.tsx @@ -1,4 +1,4 @@ -import styled, { css } from 'styled-components/macro'; +import styled, { css } from 'styled-components'; import tw from 'twin.macro'; interface Props { @@ -25,7 +25,7 @@ const Select = styled.select` display: none; } - ${(props) => + ${props => !props.hideDropdownArrow && css` ${tw`bg-neutral-600 border-neutral-500 text-neutral-200`}; diff --git a/resources/scripts/components/elements/ServerContentBlock.tsx b/resources/scripts/components/elements/ServerContentBlock.tsx index cda208c860..6905843f9f 100644 --- a/resources/scripts/components/elements/ServerContentBlock.tsx +++ b/resources/scripts/components/elements/ServerContentBlock.tsx @@ -1,5 +1,5 @@ import PageContentBlock, { PageContentBlockProps } from '@/components/elements/PageContentBlock'; -import React from 'react'; +import * as React from 'react'; import { ServerContext } from '@/state/server'; interface Props extends PageContentBlockProps { @@ -7,7 +7,7 @@ interface Props extends PageContentBlockProps { } const ServerContentBlock: React.FC = ({ title, children, ...props }) => { - const name = ServerContext.useStoreState((state) => state.server.data!.name); + const name = ServerContext.useStoreState(state => state.server.data!.name); return ( diff --git a/resources/scripts/components/elements/Spinner.tsx b/resources/scripts/components/elements/Spinner.tsx index 48de135d31..fe5ae6401c 100644 --- a/resources/scripts/components/elements/Spinner.tsx +++ b/resources/scripts/components/elements/Spinner.tsx @@ -1,19 +1,23 @@ -import React, { Suspense } from 'react'; -import styled, { css, keyframes } from 'styled-components/macro'; +import type { FC, ReactNode } from 'react'; +import { Suspense } from 'react'; +import styled, { css, keyframes } from 'styled-components'; import tw from 'twin.macro'; + import ErrorBoundary from '@/components/elements/ErrorBoundary'; export type SpinnerSize = 'small' | 'base' | 'large'; interface Props { + children?: ReactNode; + size?: SpinnerSize; centered?: boolean; isBlue?: boolean; } -interface Spinner extends React.FC { +interface Spinner extends FC { Size: Record<'SMALL' | 'BASE' | 'LARGE', SpinnerSize>; - Suspense: React.FC; + Suspense: FC; } const spin = keyframes` @@ -27,7 +31,7 @@ const SpinnerComponent = styled.div` border-radius: 50%; animation: ${spin} 1s cubic-bezier(0.55, 0.25, 0.25, 0.7) infinite; - ${(props) => + ${props => props.size === 'small' ? tw`w-4 h-4 border-2` : props.size === 'large' @@ -37,8 +41,8 @@ const SpinnerComponent = styled.div` ` : null}; - border-color: ${(props) => (!props.isBlue ? 'rgba(255, 255, 255, 0.2)' : 'hsla(212, 92%, 43%, 0.2)')}; - border-top-color: ${(props) => (!props.isBlue ? 'rgb(255, 255, 255)' : 'hsl(212, 92%, 43%)')}; + border-color: ${props => (!props.isBlue ? 'rgba(255, 255, 255, 0.2)' : 'hsla(212, 92%, 43%, 0.2)')}; + border-top-color: ${props => (!props.isBlue ? 'rgb(255, 255, 255)' : 'hsl(212, 92%, 43%)')}; `; const Spinner: Spinner = ({ centered, ...props }) => diff --git a/resources/scripts/components/elements/SpinnerOverlay.tsx b/resources/scripts/components/elements/SpinnerOverlay.tsx index d2052f3be3..0775a71f7f 100644 --- a/resources/scripts/components/elements/SpinnerOverlay.tsx +++ b/resources/scripts/components/elements/SpinnerOverlay.tsx @@ -1,17 +1,23 @@ -import React from 'react'; -import Spinner, { SpinnerSize } from '@/components/elements/Spinner'; -import Fade from '@/components/elements/Fade'; +import type { ReactNode } from 'react'; import tw from 'twin.macro'; +import Spinner, { SpinnerSize } from '@/components/elements/Spinner'; + interface Props { + children?: ReactNode; + visible: boolean; fixed?: boolean; size?: SpinnerSize; backgroundOpacity?: number; } -const SpinnerOverlay: React.FC = ({ size, fixed, visible, backgroundOpacity, children }) => ( - +function SpinnerOverlay({ size, fixed, visible, backgroundOpacity, children }: Props) { + if (!visible) { + return null; + } + + return (
= ({ size, fixed, visible, backgroundOpaci {children && (typeof children === 'string' ?

{children}

: children)}
-
-); + ); +} export default SpinnerOverlay; diff --git a/resources/scripts/components/elements/SubNavigation.tsx b/resources/scripts/components/elements/SubNavigation.tsx index 9a25b80b07..c28ae7ba31 100644 --- a/resources/scripts/components/elements/SubNavigation.tsx +++ b/resources/scripts/components/elements/SubNavigation.tsx @@ -1,4 +1,4 @@ -import styled from 'styled-components/macro'; +import styled from 'styled-components'; import tw, { theme } from 'twin.macro'; const SubNavigation = styled.div` diff --git a/resources/scripts/components/elements/Switch.tsx b/resources/scripts/components/elements/Switch.tsx index 45c7384749..03927fd635 100644 --- a/resources/scripts/components/elements/Switch.tsx +++ b/resources/scripts/components/elements/Switch.tsx @@ -1,6 +1,7 @@ -import React, { useMemo } from 'react'; -import styled from 'styled-components/macro'; -import { v4 } from 'uuid'; +import type { ChangeEvent, ReactNode } from 'react'; +import { useMemo } from 'react'; +import { nanoid } from 'nanoid'; +import styled from 'styled-components'; import tw from 'twin.macro'; import Label from '@/components/elements/Label'; import Input from '@/components/elements/Input'; @@ -42,12 +43,12 @@ export interface SwitchProps { description?: string; defaultChecked?: boolean; readOnly?: boolean; - onChange?: (e: React.ChangeEvent) => void; - children?: React.ReactNode; + onChange?: (e: ChangeEvent) => void; + children?: ReactNode; } const Switch = ({ name, label, description, defaultChecked, readOnly, onChange, children }: SwitchProps) => { - const uuid = useMemo(() => v4(), []); + const uuid = useMemo(() => nanoid(), []); return (
@@ -57,7 +58,7 @@ const Switch = ({ name, label, description, defaultChecked, readOnly, onChange, id={uuid} name={name} type={'checkbox'} - onChange={(e) => onChange && onChange(e)} + onChange={e => onChange && onChange(e)} defaultChecked={defaultChecked} disabled={readOnly} /> diff --git a/resources/scripts/components/elements/TitledGreyBox.tsx b/resources/scripts/components/elements/TitledGreyBox.tsx index e135549b12..9ccabcc71a 100644 --- a/resources/scripts/components/elements/TitledGreyBox.tsx +++ b/resources/scripts/components/elements/TitledGreyBox.tsx @@ -1,4 +1,5 @@ -import React, { memo } from 'react'; +import { memo } from 'react'; +import * as React from 'react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { IconProp } from '@fortawesome/fontawesome-svg-core'; import tw from 'twin.macro'; diff --git a/resources/scripts/components/elements/Translate.tsx b/resources/scripts/components/elements/Translate.tsx index 88dd1eab65..fac25eca84 100644 --- a/resources/scripts/components/elements/Translate.tsx +++ b/resources/scripts/components/elements/Translate.tsx @@ -1,9 +1,9 @@ -import React from 'react'; -import { Trans, TransProps, useTranslation } from 'react-i18next'; +import type { TransProps } from 'react-i18next'; +import { Trans, useTranslation } from 'react-i18next'; -type Props = Omit; +type Props = Omit, 't'>; -export default ({ ns, children, ...props }: Props) => { +function Translate({ ns, children, ...props }: Props) { const { t } = useTranslation(ns); return ( @@ -11,4 +11,6 @@ export default ({ ns, children, ...props }: Props) => { {children} ); -}; +} + +export default Translate; diff --git a/resources/scripts/components/elements/activity/ActivityLogEntry.tsx b/resources/scripts/components/elements/activity/ActivityLogEntry.tsx index 183b2ab3f5..8f9794a811 100644 --- a/resources/scripts/components/elements/activity/ActivityLogEntry.tsx +++ b/resources/scripts/components/elements/activity/ActivityLogEntry.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import * as React from 'react'; import { Link } from 'react-router-dom'; import Tooltip from '@/components/elements/tooltip/Tooltip'; import Translate from '@/components/elements/Translate'; diff --git a/resources/scripts/components/elements/activity/ActivityLogMetaButton.tsx b/resources/scripts/components/elements/activity/ActivityLogMetaButton.tsx index f1460ac7c4..aebe3e9f84 100644 --- a/resources/scripts/components/elements/activity/ActivityLogMetaButton.tsx +++ b/resources/scripts/components/elements/activity/ActivityLogMetaButton.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import { useState } from 'react'; import { ClipboardListIcon } from '@heroicons/react/outline'; import { Dialog } from '@/components/elements/dialog'; import { Button } from '@/components/elements/button/index'; diff --git a/resources/scripts/components/elements/alert/Alert.tsx b/resources/scripts/components/elements/alert/Alert.tsx index e99d6d57e4..2252e71a07 100644 --- a/resources/scripts/components/elements/alert/Alert.tsx +++ b/resources/scripts/components/elements/alert/Alert.tsx @@ -1,5 +1,5 @@ import { ExclamationIcon, ShieldExclamationIcon } from '@heroicons/react/outline'; -import React from 'react'; +import * as React from 'react'; import classNames from 'classnames'; interface AlertProps { @@ -17,7 +17,7 @@ export default ({ type, className, children }: AlertProps) => { ['border-red-500 bg-red-500/25']: type === 'danger', ['border-yellow-500 bg-yellow-500/25']: type === 'warning', }, - className + className, )} > {type === 'danger' ? ( diff --git a/resources/scripts/components/elements/button/Button.tsx b/resources/scripts/components/elements/button/Button.tsx index 61ee963111..6b140ddf17 100644 --- a/resources/scripts/components/elements/button/Button.tsx +++ b/resources/scripts/components/elements/button/Button.tsx @@ -1,4 +1,4 @@ -import React, { forwardRef } from 'react'; +import { forwardRef } from 'react'; import classNames from 'classnames'; import { ButtonProps, Options } from '@/components/elements/button/types'; import styles from './style.module.css'; @@ -17,14 +17,14 @@ const Button = forwardRef( [styles.small]: size === Options.Size.Small, [styles.large]: size === Options.Size.Large, }, - className + className, )} {...rest} > {children} ); - } + }, ); const TextButton = forwardRef(({ className, ...props }, ref) => ( diff --git a/resources/scripts/components/elements/button/types.ts b/resources/scripts/components/elements/button/types.ts index 273ac3b678..e86ec5e26f 100644 --- a/resources/scripts/components/elements/button/types.ts +++ b/resources/scripts/components/elements/button/types.ts @@ -1,15 +1,15 @@ -enum Shape { +export enum Shape { Default, IconSquare, } -enum Size { +export enum Size { Default, Small, Large, } -enum Variant { +export enum Variant { Primary, Secondary, } diff --git a/resources/scripts/components/elements/dialog/ConfirmationDialog.tsx b/resources/scripts/components/elements/dialog/ConfirmationDialog.tsx index bedfe4ee59..3f2cd7217c 100644 --- a/resources/scripts/components/elements/dialog/ConfirmationDialog.tsx +++ b/resources/scripts/components/elements/dialog/ConfirmationDialog.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import * as React from 'react'; import { Dialog, RenderDialogProps } from './'; import { Button } from '@/components/elements/button/index'; diff --git a/resources/scripts/components/elements/dialog/Dialog.tsx b/resources/scripts/components/elements/dialog/Dialog.tsx index bb35fe8fb8..7d347063e7 100644 --- a/resources/scripts/components/elements/dialog/Dialog.tsx +++ b/resources/scripts/components/elements/dialog/Dialog.tsx @@ -1,4 +1,5 @@ -import React, { useRef, useState } from 'react'; +import { useRef, useState } from 'react'; +import * as React from 'react'; import { Dialog as HDialog } from '@headlessui/react'; import { Button } from '@/components/elements/button/index'; import { XIcon } from '@heroicons/react/solid'; diff --git a/resources/scripts/components/elements/dialog/DialogFooter.tsx b/resources/scripts/components/elements/dialog/DialogFooter.tsx index 37209fce87..de0a32f91c 100644 --- a/resources/scripts/components/elements/dialog/DialogFooter.tsx +++ b/resources/scripts/components/elements/dialog/DialogFooter.tsx @@ -1,4 +1,5 @@ -import React, { useContext } from 'react'; +import { useContext } from 'react'; +import * as React from 'react'; import { DialogContext } from './'; import { useDeepCompareEffect } from '@/plugins/useDeepCompareEffect'; @@ -7,7 +8,7 @@ export default ({ children }: { children: React.ReactNode }) => { useDeepCompareEffect(() => { setFooter( -
{children}
+
{children}
, ); }, [children]); diff --git a/resources/scripts/components/elements/dialog/DialogIcon.tsx b/resources/scripts/components/elements/dialog/DialogIcon.tsx index e2ed6b207c..d075e47815 100644 --- a/resources/scripts/components/elements/dialog/DialogIcon.tsx +++ b/resources/scripts/components/elements/dialog/DialogIcon.tsx @@ -1,4 +1,4 @@ -import React, { useContext, useEffect } from 'react'; +import { useContext, useEffect } from 'react'; import { CheckIcon, ExclamationIcon, InformationCircleIcon, ShieldExclamationIcon } from '@heroicons/react/outline'; import classNames from 'classnames'; import { DialogContext, DialogIconProps, styles } from './'; @@ -19,7 +19,7 @@ export default ({ type, position, className }: DialogIconProps) => { setIcon(
-
+
, ); }, [type, className]); diff --git a/resources/scripts/components/elements/dialog/context.ts b/resources/scripts/components/elements/dialog/context.ts index b9a0f09805..dd270e149c 100644 --- a/resources/scripts/components/elements/dialog/context.ts +++ b/resources/scripts/components/elements/dialog/context.ts @@ -1,13 +1,13 @@ -import React from 'react'; +import { createContext } from 'react'; import { DialogContextType, DialogWrapperContextType } from './types'; -export const DialogContext = React.createContext({ +export const DialogContext = createContext({ setIcon: () => null, setFooter: () => null, setIconPosition: () => null, }); -export const DialogWrapperContext = React.createContext({ +export const DialogWrapperContext = createContext({ props: {}, setProps: () => null, close: () => null, diff --git a/resources/scripts/components/elements/dialog/types.d.ts b/resources/scripts/components/elements/dialog/types.d.ts index 2701b1ddaa..bb58089490 100644 --- a/resources/scripts/components/elements/dialog/types.d.ts +++ b/resources/scripts/components/elements/dialog/types.d.ts @@ -1,4 +1,4 @@ -import React from 'react'; +import * as React from 'react'; import { IconPosition } from '@/components/elements/dialog/DialogIcon'; type Callback = ((value: T) => void) | React.Dispatch>; diff --git a/resources/scripts/components/elements/dropdown/Dropdown.tsx b/resources/scripts/components/elements/dropdown/Dropdown.tsx index ef378efe5f..df77921dfd 100644 --- a/resources/scripts/components/elements/dropdown/Dropdown.tsx +++ b/resources/scripts/components/elements/dropdown/Dropdown.tsx @@ -1,4 +1,5 @@ -import React, { ElementType, forwardRef, useMemo } from 'react'; +import { ElementType, forwardRef, useMemo } from 'react'; +import * as React from 'react'; import { Menu, Transition } from '@headlessui/react'; import styles from './style.module.css'; import classNames from 'classnames'; @@ -23,8 +24,8 @@ const Dropdown = forwardRef(({ as, children }, ref) => { const list = React.Children.toArray(children) as unknown as TypedChild[]; return [ - list.filter((child) => child.type === DropdownButton), - list.filter((child) => child.type !== DropdownButton), + list.filter(child => child.type === DropdownButton), + list.filter(child => child.type !== DropdownButton), ]; }, [children]); diff --git a/resources/scripts/components/elements/dropdown/DropdownButton.tsx b/resources/scripts/components/elements/dropdown/DropdownButton.tsx index d026e2995d..13e3e38fc9 100644 --- a/resources/scripts/components/elements/dropdown/DropdownButton.tsx +++ b/resources/scripts/components/elements/dropdown/DropdownButton.tsx @@ -2,7 +2,7 @@ import classNames from 'classnames'; import styles from '@/components/elements/dropdown/style.module.css'; import { ChevronDownIcon } from '@heroicons/react/solid'; import { Menu } from '@headlessui/react'; -import React from 'react'; +import * as React from 'react'; interface Props { className?: string; diff --git a/resources/scripts/components/elements/dropdown/DropdownItem.tsx b/resources/scripts/components/elements/dropdown/DropdownItem.tsx index 1c012e15ad..d9f0cf68fd 100644 --- a/resources/scripts/components/elements/dropdown/DropdownItem.tsx +++ b/resources/scripts/components/elements/dropdown/DropdownItem.tsx @@ -1,4 +1,5 @@ -import React, { forwardRef } from 'react'; +import { forwardRef } from 'react'; +import * as React from 'react'; import { Menu } from '@headlessui/react'; import styles from './style.module.css'; import classNames from 'classnames'; @@ -26,7 +27,7 @@ const DropdownItem = forwardRef( [styles.danger]: danger, [styles.disabled]: disabled, }, - className + className, )} onClick={onClick} > @@ -36,7 +37,7 @@ const DropdownItem = forwardRef( )} ); - } + }, ); export default DropdownItem; diff --git a/resources/scripts/components/elements/editor/Editor.tsx b/resources/scripts/components/elements/editor/Editor.tsx new file mode 100644 index 0000000000..bc395d6013 --- /dev/null +++ b/resources/scripts/components/elements/editor/Editor.tsx @@ -0,0 +1,206 @@ +import { autocompletion, completionKeymap, closeBrackets, closeBracketsKeymap } from '@codemirror/autocomplete'; +import { defaultKeymap, history, historyKeymap, indentWithTab } from '@codemirror/commands'; +import { + defaultHighlightStyle, + syntaxHighlighting, + indentOnInput, + bracketMatching, + foldGutter, + foldKeymap, + LanguageDescription, + indentUnit, +} from '@codemirror/language'; +import { languages } from '@codemirror/language-data'; +import { lintKeymap } from '@codemirror/lint'; +import { searchKeymap, highlightSelectionMatches } from '@codemirror/search'; +import type { Extension } from '@codemirror/state'; +import { EditorState, Compartment } from '@codemirror/state'; +import { + keymap, + highlightSpecialChars, + drawSelection, + highlightActiveLine, + dropCursor, + rectangularSelection, + crosshairCursor, + lineNumbers, + highlightActiveLineGutter, + EditorView, +} from '@codemirror/view'; +import type { CSSProperties } from 'react'; +import { useEffect, useRef, useState } from 'react'; + +import { ayuMirageHighlightStyle, ayuMirageTheme } from './theme'; + +function findLanguageByFilename(filename: string): LanguageDescription | undefined { + const language = LanguageDescription.matchFilename(languages, filename); + if (language !== null) { + return language; + } + + return undefined; +} + +const defaultExtensions: Extension = [ + // Ayu Mirage + ayuMirageTheme, + syntaxHighlighting(ayuMirageHighlightStyle), + + lineNumbers(), + highlightActiveLineGutter(), + highlightSpecialChars(), + history(), + foldGutter(), + drawSelection(), + dropCursor(), + EditorState.allowMultipleSelections.of(true), + indentOnInput(), + syntaxHighlighting(defaultHighlightStyle, { fallback: true }), + bracketMatching(), + closeBrackets(), + autocompletion(), + rectangularSelection(), + crosshairCursor(), + highlightActiveLine(), + highlightSelectionMatches(), + keymap.of([ + ...closeBracketsKeymap, + ...defaultKeymap, + ...searchKeymap, + ...historyKeymap, + ...foldKeymap, + ...completionKeymap, + ...lintKeymap, + indentWithTab, + ]), + EditorState.tabSize.of(4), + indentUnit.of('\t'), +]; + +export interface EditorProps { + // DOM + className?: string; + style?: CSSProperties; + + // CodeMirror Config + extensions?: Extension[]; + language?: LanguageDescription; + + // Options + filename?: string; + initialContent?: string; + + // ? + fetchContent?: (callback: () => Promise) => void; + + // Events + onContentSaved?: () => void; + onLanguageChanged?: (language: LanguageDescription | undefined) => void; +} + +export function Editor(props: EditorProps) { + const ref = useRef(null); + + const [view, setView] = useState(); + + // eslint-disable-next-line react/hook-use-state + const [languageConfig] = useState(new Compartment()); + // eslint-disable-next-line react/hook-use-state + const [keybindings] = useState(new Compartment()); + + const createEditorState = () => + EditorState.create({ + doc: props.initialContent, + extensions: [ + defaultExtensions, + props.extensions === undefined ? [] : props.extensions, + languageConfig.of([]), + keybindings.of([]), + ], + }); + + useEffect(() => { + if (ref.current === null) { + return; + } + + setView( + new EditorView({ + state: createEditorState(), + parent: ref.current, + }), + ); + + return () => { + if (view === undefined) { + return; + } + + view.destroy(); + setView(undefined); + }; + }, [ref]); + + useEffect(() => { + if (view === undefined) { + return; + } + + view.setState(createEditorState()); + }, [props.initialContent]); + + useEffect(() => { + if (view === undefined) { + return; + } + + const language = props.language ?? findLanguageByFilename(props.filename ?? ''); + if (language === undefined) { + return; + } + + void language.load().then(language => { + view.dispatch({ + effects: languageConfig.reconfigure(language), + }); + }); + + if (props.onLanguageChanged !== undefined) { + props.onLanguageChanged(language); + } + }, [view, props.filename, props.language]); + + useEffect(() => { + if (props.fetchContent === undefined) { + return; + } + + if (!view) { + props.fetchContent(async () => { + throw new Error('no editor session has been configured'); + }); + return; + } + + const { onContentSaved } = props; + if (onContentSaved !== undefined) { + view.dispatch({ + effects: keybindings.reconfigure( + keymap.of([ + { + key: 'Mod-s', + run() { + onContentSaved(); + return true; + }, + }, + ]), + ), + }); + } + + props.fetchContent(async () => view.state.doc.toJSON().join('\n')); + }, [view, props.fetchContent, props.onContentSaved]); + + return
; +} diff --git a/resources/scripts/components/elements/editor/index.ts b/resources/scripts/components/elements/editor/index.ts new file mode 100644 index 0000000000..5ccde0efcb --- /dev/null +++ b/resources/scripts/components/elements/editor/index.ts @@ -0,0 +1 @@ +export { Editor } from './Editor'; diff --git a/resources/scripts/components/elements/editor/theme.ts b/resources/scripts/components/elements/editor/theme.ts new file mode 100644 index 0000000000..7e212fca16 --- /dev/null +++ b/resources/scripts/components/elements/editor/theme.ts @@ -0,0 +1,148 @@ +import { HighlightStyle } from '@codemirror/language'; +import type { Extension } from '@codemirror/state'; +import { EditorView } from '@codemirror/view'; +import { tags as t } from '@lezer/highlight'; + +const highlightBackground = 'transparent'; +const background = '#1F2430'; +const selection = '#34455A'; +const cursor = '#FFCC66'; + +export const ayuMirageTheme: Extension = EditorView.theme( + { + '&': { + color: '#CBCCC6', + backgroundColor: background, + }, + + '.cm-content': { + caretColor: cursor, + }, + + '&.cm-focused .cm-cursor': { borderLeftColor: cursor }, + '&.cm-focused .cm-selectionBackground, .cm-selectionBackground, ::selection': { + backgroundColor: selection, + }, + + '.cm-panels': { backgroundColor: '#232834', color: '#CBCCC6' }, + '.cm-panels.cm-panels-top': { borderBottom: '2px solid black' }, + '.cm-panels.cm-panels-bottom': { borderTop: '2px solid black' }, + + '.cm-searchMatch': { + backgroundColor: '#72a1ff59', + outline: '1px solid #457dff', + }, + '.cm-searchMatch.cm-searchMatch-selected': { + backgroundColor: '#6199ff2f', + }, + + '.cm-activeLine': { backgroundColor: highlightBackground }, + '.cm-selectionMatch': { backgroundColor: '#aafe661a' }, + + '.cm-matchingBracket, .cm-nonmatchingBracket': { + backgroundColor: '#bad0f847', + outline: '1px solid #515a6b', + }, + + '.cm-gutters': { + backgroundColor: 'transparent', + color: '#FF3333', + border: 'none', + }, + + '.cm-gutterElement': { + color: 'rgba(61, 66, 77, 99)', + }, + + '.cm-activeLineGutter': { + backgroundColor: highlightBackground, + }, + + '.cm-foldPlaceholder': { + backgroundColor: 'transparent', + border: 'none', + color: '#ddd', + }, + + '.cm-tooltip': { + border: '1px solid #181a1f', + backgroundColor: '#232834', + }, + '.cm-tooltip-autocomplete': { + '& > ul > li[aria-selected]': { + backgroundColor: highlightBackground, + color: '#CBCCC6', + }, + }, + }, + { dark: true }, +); + +export const ayuMirageHighlightStyle = HighlightStyle.define([ + { + tag: t.keyword, + color: '#FFA759', + }, + { + tag: [t.name, t.deleted, t.character, t.propertyName, t.macroName], + color: '#5CCFE6', + }, + { + tag: [t.function(t.variableName), t.labelName], + color: '#CBCCC6', + }, + { + tag: [t.color, t.constant(t.name), t.standard(t.name)], + color: '#F29E74', + }, + { + tag: [t.definition(t.name), t.separator], + color: '#CBCCC6B3', + }, + { + tag: [t.typeName, t.className, t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], + color: '#FFCC66', + }, + { + tag: [t.operator, t.operatorKeyword, t.url, t.escape, t.regexp, t.link, t.special(t.string)], + color: '#5CCFE6', + }, + { + tag: [t.meta, t.comment], + color: '#5C6773', + }, + { + tag: t.strong, + fontWeight: 'bold', + }, + { + tag: t.emphasis, + fontStyle: 'italic', + }, + { + tag: t.strikethrough, + textDecoration: 'line-through', + }, + { + tag: t.link, + color: '#FF3333', + textDecoration: 'underline', + }, + { + tag: t.heading, + fontWeight: 'bold', + color: '#BAE67E', + }, + { + tag: [t.atom, t.bool, t.special(t.variableName)], + color: '#5CCFE6', + }, + { + tag: [t.processingInstruction, t.string, t.inserted], + color: '#BAE67E', + }, + { + tag: t.invalid, + color: '#FF3333', + }, +]); diff --git a/resources/scripts/components/elements/inputs/Checkbox.tsx b/resources/scripts/components/elements/inputs/Checkbox.tsx index 82233b15ff..e9c043b616 100644 --- a/resources/scripts/components/elements/inputs/Checkbox.tsx +++ b/resources/scripts/components/elements/inputs/Checkbox.tsx @@ -1,4 +1,5 @@ -import React, { forwardRef } from 'react'; +import { forwardRef } from 'react'; +import * as React from 'react'; import classNames from 'classnames'; import styles from './styles.module.css'; diff --git a/resources/scripts/components/elements/inputs/InputField.tsx b/resources/scripts/components/elements/inputs/InputField.tsx index c3dfc3ccdc..ac92e17f12 100644 --- a/resources/scripts/components/elements/inputs/InputField.tsx +++ b/resources/scripts/components/elements/inputs/InputField.tsx @@ -1,4 +1,5 @@ -import React, { forwardRef } from 'react'; +import { forwardRef } from 'react'; +import * as React from 'react'; import classNames from 'classnames'; import styles from './styles.module.css'; @@ -19,7 +20,7 @@ const Component = forwardRef(({ className, va 'form-input', styles.text_input, { [styles.loose]: variant === Variant.Loose }, - className + className, )} {...props} /> diff --git a/resources/scripts/components/elements/inputs/index.ts b/resources/scripts/components/elements/inputs/index.ts index b961b0764e..3b7f15d773 100644 --- a/resources/scripts/components/elements/inputs/index.ts +++ b/resources/scripts/components/elements/inputs/index.ts @@ -6,7 +6,7 @@ const Input = Object.assign( { Text: InputField, Checkbox: Checkbox, - } + }, ); export { Input }; diff --git a/resources/scripts/components/elements/table/PaginationFooter.tsx b/resources/scripts/components/elements/table/PaginationFooter.tsx index 0484065ed2..d578ae4d93 100644 --- a/resources/scripts/components/elements/table/PaginationFooter.tsx +++ b/resources/scripts/components/elements/table/PaginationFooter.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import { PaginationDataSet } from '@/api/http'; import classNames from 'classnames'; import { Button } from '@/components/elements/button/index'; @@ -53,7 +52,7 @@ const PaginationFooter = ({ pagination, className, onPageSelect }: Props) => { - {pages.previous.reverse().map((value) => ( + {pages.previous.reverse().map(value => ( {value} @@ -61,7 +60,7 @@ const PaginationFooter = ({ pagination, className, onPageSelect }: Props) => { - {pages.next.map((value) => ( + {pages.next.map(value => ( {value} diff --git a/resources/scripts/components/elements/tooltip/Tooltip.tsx b/resources/scripts/components/elements/tooltip/Tooltip.tsx index ce488dc4fb..df24d38cce 100644 --- a/resources/scripts/components/elements/tooltip/Tooltip.tsx +++ b/resources/scripts/components/elements/tooltip/Tooltip.tsx @@ -1,4 +1,5 @@ -import React, { cloneElement, useRef, useState } from 'react'; +import { cloneElement, useRef, useState } from 'react'; +import * as React from 'react'; import { arrow, autoUpdate, @@ -104,7 +105,7 @@ export default ({ children, ...props }: Props) => { ref={arrowEl} style={{ transform: `translate(${Math.round(ax || 0)}px, ${Math.round( - ay || 0 + ay || 0, )}px) rotate(45deg)`, }} className={classNames('absolute bg-gray-900 w-3 h-3', side)} diff --git a/resources/scripts/components/elements/transitions/FadeTransition.tsx b/resources/scripts/components/elements/transitions/FadeTransition.tsx index e4287cc18e..be0dd64257 100644 --- a/resources/scripts/components/elements/transitions/FadeTransition.tsx +++ b/resources/scripts/components/elements/transitions/FadeTransition.tsx @@ -1,16 +1,18 @@ -import React from 'react'; import { Transition } from '@headlessui/react'; +import type { ElementType, ReactNode } from 'react'; type Duration = `duration-${number}`; interface Props { - as?: React.ElementType; + as?: ElementType; duration?: Duration | [Duration, Duration]; + appear?: boolean; + unmount?: boolean; show: boolean; - children: React.ReactNode; + children: ReactNode; } -export default ({ children, duration, ...props }: Props) => { +function FadeTransition({ children, duration, ...props }: Props) { const [enterDuration, exitDuration] = Array.isArray(duration) ? duration : !duration @@ -30,4 +32,6 @@ export default ({ children, duration, ...props }: Props) => { {children} ); -}; +} + +export default FadeTransition; diff --git a/resources/scripts/components/history.ts b/resources/scripts/components/history.ts deleted file mode 100644 index 5f339d963f..0000000000 --- a/resources/scripts/components/history.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { createBrowserHistory } from 'history'; - -export const history = createBrowserHistory({ basename: '/' }); diff --git a/resources/scripts/components/server/ConflictStateRenderer.tsx b/resources/scripts/components/server/ConflictStateRenderer.tsx index 95e70bbaa3..8bba31ca71 100644 --- a/resources/scripts/components/server/ConflictStateRenderer.tsx +++ b/resources/scripts/components/server/ConflictStateRenderer.tsx @@ -1,15 +1,14 @@ -import React from 'react'; -import { ServerContext } from '@/state/server'; -import ScreenBlock from '@/components/elements/ScreenBlock'; import ServerInstallSvg from '@/assets/images/server_installing.svg'; import ServerErrorSvg from '@/assets/images/server_error.svg'; import ServerRestoreSvg from '@/assets/images/server_restore.svg'; +import ScreenBlock from '@/components/elements/ScreenBlock'; +import { ServerContext } from '@/state/server'; export default () => { - const status = ServerContext.useStoreState((state) => state.server.data?.status || null); - const isTransferring = ServerContext.useStoreState((state) => state.server.data?.isTransferring || false); + const status = ServerContext.useStoreState(state => state.server.data?.status || null); + const isTransferring = ServerContext.useStoreState(state => state.server.data?.isTransferring || false); const isNodeUnderMaintenance = ServerContext.useStoreState( - (state) => state.server.data?.isNodeUnderMaintenance || false + state => state.server.data?.isNodeUnderMaintenance || false, ); return status === 'installing' || status === 'install_failed' ? ( @@ -36,7 +35,7 @@ export default () => { image={ServerRestoreSvg} message={ isTransferring - ? 'Your server is being transfered to a new node, please check back later.' + ? 'Your server is being transferred to a new node, please check back later.' : 'Your server is currently being restored from a backup, please check back in a few minutes.' } /> diff --git a/resources/scripts/components/server/InstallListener.tsx b/resources/scripts/components/server/InstallListener.tsx index b4e3a0e3e5..fbf377a2c3 100644 --- a/resources/scripts/components/server/InstallListener.tsx +++ b/resources/scripts/components/server/InstallListener.tsx @@ -5,26 +5,26 @@ import { mutate } from 'swr'; import { getDirectorySwrKey } from '@/plugins/useFileManagerSwr'; const InstallListener = () => { - const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid); - const getServer = ServerContext.useStoreActions((actions) => actions.server.getServer); - const setServerFromState = ServerContext.useStoreActions((actions) => actions.server.setServerFromState); + const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); + const getServer = ServerContext.useStoreActions(actions => actions.server.getServer); + const setServerFromState = ServerContext.useStoreActions(actions => actions.server.setServerFromState); useWebsocketEvent(SocketEvent.BACKUP_RESTORE_COMPLETED, () => { mutate(getDirectorySwrKey(uuid, '/'), undefined); - setServerFromState((s) => ({ ...s, status: null })); + setServerFromState(s => ({ ...s, status: null })); }); // Listen for the installation completion event and then fire off a request to fetch the updated // server information. This allows the server to automatically become available to the user if they // just sit on the page. useWebsocketEvent(SocketEvent.INSTALL_COMPLETED, () => { - getServer(uuid).catch((error) => console.error(error)); + getServer(uuid).catch(error => console.error(error)); }); // When we see the install started event immediately update the state to indicate such so that the // screens automatically update. useWebsocketEvent(SocketEvent.INSTALL_STARTED, () => { - setServerFromState((s) => ({ ...s, status: 'installing' })); + setServerFromState(s => ({ ...s, status: 'installing' })); }); return null; diff --git a/resources/scripts/components/server/ServerActivityLogContainer.tsx b/resources/scripts/components/server/ServerActivityLogContainer.tsx index 5319454864..a617d1d2db 100644 --- a/resources/scripts/components/server/ServerActivityLogContainer.tsx +++ b/resources/scripts/components/server/ServerActivityLogContainer.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; import { useActivityLogs } from '@/api/server/activity'; import ServerContentBlock from '@/components/elements/ServerContentBlock'; import { useFlashKey } from '@/plugins/useFlash'; @@ -24,7 +24,7 @@ export default () => { }); useEffect(() => { - setFilters((value) => ({ ...value, filters: { ip: hash.ip, event: hash.event } })); + setFilters(value => ({ ...value, filters: { ip: hash.ip, event: hash.event } })); }, [hash]); useEffect(() => { @@ -39,7 +39,7 @@ export default () => { setFilters((value) => ({ ...value, filters: {} }))} + onClick={() => setFilters(value => ({ ...value, filters: {} }))} > Clear Filters @@ -51,7 +51,7 @@ export default () => {

No activity logs available for this server.

) : (
- {data?.items.map((activity) => ( + {data?.items.map(activity => ( @@ -61,7 +61,7 @@ export default () => { {data && ( setFilters((value) => ({ ...value, page }))} + onPageSelect={page => setFilters(value => ({ ...value, page }))} /> )} diff --git a/resources/scripts/components/server/TransferListener.tsx b/resources/scripts/components/server/TransferListener.tsx index 4d94217453..10544f547d 100644 --- a/resources/scripts/components/server/TransferListener.tsx +++ b/resources/scripts/components/server/TransferListener.tsx @@ -3,19 +3,19 @@ import { ServerContext } from '@/state/server'; import { SocketEvent } from '@/components/server/events'; const TransferListener = () => { - const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid); - const getServer = ServerContext.useStoreActions((actions) => actions.server.getServer); - const setServerFromState = ServerContext.useStoreActions((actions) => actions.server.setServerFromState); + const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); + const getServer = ServerContext.useStoreActions(actions => actions.server.getServer); + const setServerFromState = ServerContext.useStoreActions(actions => actions.server.setServerFromState); // Listen for the transfer status event, so we can update the state of the server. useWebsocketEvent(SocketEvent.TRANSFER_STATUS, (status: string) => { if (status === 'pending' || status === 'processing') { - setServerFromState((s) => ({ ...s, isTransferring: true })); + setServerFromState(s => ({ ...s, isTransferring: true })); return; } if (status === 'failed') { - setServerFromState((s) => ({ ...s, isTransferring: false })); + setServerFromState(s => ({ ...s, isTransferring: false })); return; } @@ -24,7 +24,7 @@ const TransferListener = () => { } // Refresh the server's information as it's node and allocations were just updated. - getServer(uuid).catch((error) => console.error(error)); + getServer(uuid).catch(error => console.error(error)); }); return null; diff --git a/resources/scripts/components/server/UptimeDuration.tsx b/resources/scripts/components/server/UptimeDuration.tsx index 6623de0b81..e87700a61b 100644 --- a/resources/scripts/components/server/UptimeDuration.tsx +++ b/resources/scripts/components/server/UptimeDuration.tsx @@ -1,5 +1,3 @@ -import React from 'react'; - export default ({ uptime }: { uptime: number }) => { const days = Math.floor(uptime / (24 * 60 * 60)); const hours = Math.floor((Math.floor(uptime) / 60 / 60) % 24); diff --git a/resources/scripts/components/server/WebsocketHandler.tsx b/resources/scripts/components/server/WebsocketHandler.tsx index 2772013dfe..954acda2cb 100644 --- a/resources/scripts/components/server/WebsocketHandler.tsx +++ b/resources/scripts/components/server/WebsocketHandler.tsx @@ -1,29 +1,32 @@ -import React, { useEffect, useState } from 'react'; -import { Websocket } from '@/plugins/Websocket'; -import { ServerContext } from '@/state/server'; +import { useEffect, useState } from 'react'; +import tw from 'twin.macro'; + import getWebsocketToken from '@/api/server/getWebsocketToken'; import ContentContainer from '@/components/elements/ContentContainer'; -import { CSSTransition } from 'react-transition-group'; import Spinner from '@/components/elements/Spinner'; -import tw from 'twin.macro'; +import FadeTransition from '@/components/elements/transitions/FadeTransition'; +import { Websocket } from '@/plugins/Websocket'; +import { ServerContext } from '@/state/server'; const reconnectErrors = ['jwt: exp claim is invalid', 'jwt: created too far in past (denylist)']; -export default () => { +function WebsocketHandler() { let updatingToken = false; const [error, setError] = useState<'connecting' | string>(''); - const { connected, instance } = ServerContext.useStoreState((state) => state.socket); - const uuid = ServerContext.useStoreState((state) => state.server.data?.uuid); - const setServerStatus = ServerContext.useStoreActions((actions) => actions.status.setServerStatus); - const { setInstance, setConnectionState } = ServerContext.useStoreActions((actions) => actions.socket); + const { connected, instance } = ServerContext.useStoreState(state => state.socket); + const uuid = ServerContext.useStoreState(state => state.server.data?.uuid); + 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; + if (updatingToken) { + return; + } updatingToken = true; getWebsocketToken(uuid) - .then((data) => socket.setToken(data.token, true)) - .catch((error) => console.error(error)) + .then(data => socket.setToken(data.token, true)) + .catch(error => console.error(error)) .then(() => { updatingToken = false; }); @@ -38,9 +41,9 @@ export default () => { setError('connecting'); setConnectionState(false); }); - socket.on('status', (status) => setServerStatus(status)); + socket.on('status', status => setServerStatus(status)); - socket.on('daemon error', (message) => { + socket.on('daemon error', message => { console.warn('Got error message from daemon socket:', message); }); @@ -50,11 +53,11 @@ export default () => { setConnectionState(false); console.warn('JWT validation error from wings:', error); - if (reconnectErrors.find((v) => error.toLowerCase().indexOf(v) >= 0)) { + if (reconnectErrors.find(v => error.toLowerCase().indexOf(v) >= 0)) { updateToken(uuid, socket); } else { setError( - 'There was an error validating the credentials provided for the websocket. Please refresh the page.' + 'There was an error validating the credentials provided for the websocket. Please refresh the page.', ); } }); @@ -74,14 +77,14 @@ export default () => { }); getWebsocketToken(uuid) - .then((data) => { + .then(data => { // Connect and then set the authentication token. socket.setToken(data.token).connect(data.socket); // Once that is done, set the instance. setInstance(socket); }) - .catch((error) => console.error(error)); + .catch(error => console.error(error)); }; useEffect(() => { @@ -105,7 +108,7 @@ export default () => { }, [uuid]); return error ? ( - +
{error === 'connecting' ? ( @@ -120,6 +123,8 @@ export default () => { )}
-
+ ) : null; -}; +} + +export default WebsocketHandler; diff --git a/resources/scripts/components/server/backups/BackupContainer.tsx b/resources/scripts/components/server/backups/BackupContainer.tsx index 0134142bdf..a980920c0c 100644 --- a/resources/scripts/components/server/backups/BackupContainer.tsx +++ b/resources/scripts/components/server/backups/BackupContainer.tsx @@ -1,4 +1,4 @@ -import React, { useContext, useEffect, useState } from 'react'; +import { useContext, useEffect, useState } from 'react'; import Spinner from '@/components/elements/Spinner'; import useFlash from '@/plugins/useFlash'; import Can from '@/components/elements/Can'; @@ -16,7 +16,7 @@ const BackupContainer = () => { const { clearFlashes, clearAndAddHttpError } = useFlash(); const { data: backups, error, isValidating } = getServerBackups(); - const backupLimit = ServerContext.useStoreState((state) => state.server.data!.featureLimits.backups); + const backupLimit = ServerContext.useStoreState(state => state.server.data!.featureLimits.backups); useEffect(() => { if (!error) { diff --git a/resources/scripts/components/server/backups/BackupContextMenu.tsx b/resources/scripts/components/server/backups/BackupContextMenu.tsx index 43c8b35754..4e371f2bdc 100644 --- a/resources/scripts/components/server/backups/BackupContextMenu.tsx +++ b/resources/scripts/components/server/backups/BackupContextMenu.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import { useState } from 'react'; import { faBoxOpen, faCloudDownloadAlt, @@ -28,8 +28,8 @@ interface Props { } export default ({ backup }: Props) => { - const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid); - const setServerFromState = ServerContext.useStoreActions((actions) => actions.server.setServerFromState); + const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); + const setServerFromState = ServerContext.useStoreActions(actions => actions.server.setServerFromState); const [modal, setModal] = useState(''); const [loading, setLoading] = useState(false); const [truncate, setTruncate] = useState(false); @@ -40,11 +40,11 @@ export default ({ backup }: Props) => { setLoading(true); clearFlashes('backups'); getBackupDownloadUrl(uuid, backup.uuid) - .then((url) => { + .then(url => { // @ts-expect-error this is valid window.location = url; }) - .catch((error) => { + .catch(error => { console.error(error); clearAndAddHttpError({ key: 'backups', error }); }) @@ -55,17 +55,18 @@ export default ({ backup }: Props) => { setLoading(true); clearFlashes('backups'); deleteBackup(uuid, backup.uuid) - .then(() => - mutate( - (data) => ({ - ...data, - items: data.items.filter((b) => b.uuid !== backup.uuid), - backupCount: data.backupCount - 1, - }), - false - ) + .then( + async () => + await mutate( + data => ({ + ...data!, + items: data!.items.filter(b => b.uuid !== backup.uuid), + backupCount: data!.backupCount - 1, + }), + false, + ), ) - .catch((error) => { + .catch(error => { console.error(error); clearAndAddHttpError({ key: 'backups', error }); setLoading(false); @@ -78,12 +79,12 @@ export default ({ backup }: Props) => { clearFlashes('backups'); restoreServerBackup(uuid, backup.uuid, truncate) .then(() => - setServerFromState((s) => ({ + setServerFromState(s => ({ ...s, status: 'restoring_backup', - })) + })), ) - .catch((error) => { + .catch(error => { console.error(error); clearAndAddHttpError({ key: 'backups', error }); }) @@ -97,23 +98,24 @@ export default ({ backup }: Props) => { } 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 - ) + .then( + async () => + await mutate( + data => ({ + ...data!, + items: data!.items.map(b => + b.uuid !== backup.uuid + ? b + : { + ...b, + isLocked: !b.isLocked, + }, + ), + }), + false, + ), ) - .catch((error) => alert(httpErrorToHuman(error))) + .catch(error => alert(httpErrorToHuman(error))) .then(() => setModal('')); }; @@ -146,7 +148,7 @@ export default ({ backup }: Props) => { id={'restore_truncate'} value={'true'} checked={truncate} - onChange={() => setTruncate((s) => !s)} + onChange={() => setTruncate(s => !s)} /> Delete all files before restoring backup. @@ -164,7 +166,7 @@ export default ({ backup }: Props) => { {backup.isSuccessful ? ( ( + renderToggle={onClick => (
); -}; +} + +export default StatBlock; diff --git a/resources/scripts/components/server/console/StatGraphs.tsx b/resources/scripts/components/server/console/StatGraphs.tsx index 9af405ba62..a7ab28f45b 100644 --- a/resources/scripts/components/server/console/StatGraphs.tsx +++ b/resources/scripts/components/server/console/StatGraphs.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useRef } from 'react'; +import { useEffect, useRef } from 'react'; import { ServerContext } from '@/state/server'; import { SocketEvent } from '@/components/server/events'; import useWebsocketEvent from '@/plugins/useWebsocketEvent'; @@ -12,8 +12,8 @@ import ChartBlock from '@/components/server/console/ChartBlock'; import Tooltip from '@/components/elements/tooltip/Tooltip'; export default () => { - const status = ServerContext.useStoreState((state) => state.status.value); - const limits = ServerContext.useStoreState((state) => state.server.data!.limits); + const status = ServerContext.useStoreState(state => state.status.value); + const limits = ServerContext.useStoreState(state => state.server.data!.limits); const previous = useRef>({ tx: -1, rx: -1 }); const cpu = useChartTickLabel('CPU', limits.cpu, '%', 2); diff --git a/resources/scripts/components/server/console/chart.ts b/resources/scripts/components/server/console/chart.ts index 65f919aa13..d495cbbfc5 100644 --- a/resources/scripts/components/server/console/chart.ts +++ b/resources/scripts/components/server/console/chart.ts @@ -71,13 +71,14 @@ const options: ChartOptions<'line'> = { }; function getOptions(opts?: DeepPartial> | undefined): ChartOptions<'line'> { - return deepmerge(options, opts || {}); + // @ts-expect-error go away + return deepmerge(options, opts ?? {}); } type ChartDatasetCallback = (value: ChartDataset<'line'>, index: number) => ChartDataset<'line'>; function getEmptyData(label: string, sets = 1, callback?: ChartDatasetCallback | undefined): ChartData<'line'> { - const next = callback || ((value) => value); + const next = callback || (value => value); return { labels: Array(20) @@ -94,8 +95,8 @@ function getEmptyData(label: string, sets = 1, callback?: ChartDatasetCallback | borderColor: theme('colors.cyan.400'), backgroundColor: hexToRgba(theme('colors.cyan.700'), 0.5), }, - index - ) + index, + ), ), }; } @@ -110,30 +111,31 @@ interface UseChartOptions { function useChart(label: string, opts?: UseChartOptions) { const options = getOptions( - typeof opts?.options === 'number' ? { scales: { y: { min: 0, suggestedMax: opts.options } } } : opts?.options + typeof opts?.options === 'number' ? { scales: { y: { min: 0, suggestedMax: opts.options } } } : opts?.options, ); const [data, setData] = useState(getEmptyData(label, opts?.sets || 1, opts?.callback)); const push = (items: number | null | (number | null)[]) => - setData((state) => + setData(state => merge(state, { datasets: (Array.isArray(items) ? items : [items]).map((item, index) => ({ ...state.datasets[index], - data: state.datasets[index].data - .slice(1) - .concat(typeof item === 'number' ? Number(item.toFixed(2)) : item), + data: + state.datasets[index]?.data + ?.slice(1) + ?.concat(typeof item === 'number' ? Number(item.toFixed(2)) : item) ?? [], })), - }) + }), ); const clear = () => - setData((state) => + setData(state => merge(state, { - datasets: state.datasets.map((value) => ({ + datasets: state.datasets.map(value => ({ ...value, data: Array(20).fill(-5), })), - }) + }), ); return { props: { data, options }, push, clear }; diff --git a/resources/scripts/components/server/databases/CreateDatabaseButton.tsx b/resources/scripts/components/server/databases/CreateDatabaseButton.tsx index 24c19e040a..a3c85e288c 100644 --- a/resources/scripts/components/server/databases/CreateDatabaseButton.tsx +++ b/resources/scripts/components/server/databases/CreateDatabaseButton.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import { useState } from 'react'; import Modal from '@/components/elements/Modal'; import { Form, Formik, FormikHelpers } from 'formik'; import Field from '@/components/elements/Field'; @@ -23,17 +23,17 @@ const schema = object().shape({ .max(48, 'Database name must not exceed 48 characters.') .matches( /^[\w\-.]{3,48}$/, - 'Database name should only contain alphanumeric characters, underscores, dashes, and/or periods.' + 'Database name should only contain alphanumeric characters, underscores, dashes, and/or periods.', ), connectionsFrom: string().matches(/^[\w\-/.%:]+$/, 'A valid host address must be provided.'), }); export default () => { - const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid); + const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); const { addError, clearFlashes } = useFlash(); const [visible, setVisible] = useState(false); - const appendDatabase = ServerContext.useStoreActions((actions) => actions.databases.appendDatabase); + const appendDatabase = ServerContext.useStoreActions(actions => actions.databases.appendDatabase); const submit = (values: Values, { setSubmitting }: FormikHelpers) => { clearFlashes('database:create'); @@ -41,11 +41,11 @@ export default () => { databaseName: values.databaseName, connectionsFrom: values.connectionsFrom || '%', }) - .then((database) => { + .then(database => { appendDatabase(database); setVisible(false); }) - .catch((error) => { + .catch(error => { addError({ key: 'database:create', message: httpErrorToHuman(error) }); setSubmitting(false); }); diff --git a/resources/scripts/components/server/databases/DatabaseRow.tsx b/resources/scripts/components/server/databases/DatabaseRow.tsx index 9d2c05a73c..32c9f66598 100644 --- a/resources/scripts/components/server/databases/DatabaseRow.tsx +++ b/resources/scripts/components/server/databases/DatabaseRow.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import { useState } from 'react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faDatabase, faEye, faTrashAlt } from '@fortawesome/free-solid-svg-icons'; import Modal from '@/components/elements/Modal'; @@ -26,13 +26,13 @@ interface Props { } export default ({ database, className }: Props) => { - const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid); + const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); const { addError, clearFlashes } = useFlash(); const [visible, setVisible] = useState(false); const [connectionVisible, setConnectionVisible] = useState(false); - const appendDatabase = ServerContext.useStoreActions((actions) => actions.databases.appendDatabase); - const removeDatabase = ServerContext.useStoreActions((actions) => actions.databases.removeDatabase); + const appendDatabase = ServerContext.useStoreActions(actions => actions.databases.appendDatabase); + const removeDatabase = ServerContext.useStoreActions(actions => actions.databases.removeDatabase); const jdbcConnectionString = `jdbc:mysql://${database.username}${ database.password ? `:${encodeURIComponent(database.password)}` : '' @@ -44,14 +44,14 @@ export default ({ database, className }: Props) => { .oneOf([database.name.split('_', 2)[1], database.name], 'The database name must be provided.'), }); - const submit = (values: { confirm: string }, { setSubmitting }: FormikHelpers<{ confirm: string }>) => { + const submit = (_: { confirm: string }, { setSubmitting }: FormikHelpers<{ confirm: string }>) => { clearFlashes(); deleteServerDatabase(uuid, database.id) .then(() => { setVisible(false); setTimeout(() => removeDatabase(database.id), 150); }) - .catch((error) => { + .catch(error => { console.error(error); setSubmitting(false); addError({ key: 'database:delete', message: httpErrorToHuman(error) }); diff --git a/resources/scripts/components/server/databases/DatabasesContainer.tsx b/resources/scripts/components/server/databases/DatabasesContainer.tsx index 16b690014d..6d74bb9651 100644 --- a/resources/scripts/components/server/databases/DatabasesContainer.tsx +++ b/resources/scripts/components/server/databases/DatabasesContainer.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; import getServerDatabases from '@/api/server/databases/getServerDatabases'; import { ServerContext } from '@/state/server'; import { httpErrorToHuman } from '@/api/http'; @@ -9,27 +9,27 @@ import CreateDatabaseButton from '@/components/server/databases/CreateDatabaseBu import Can from '@/components/elements/Can'; import useFlash from '@/plugins/useFlash'; import tw from 'twin.macro'; -import Fade from '@/components/elements/Fade'; import ServerContentBlock from '@/components/elements/ServerContentBlock'; import { useDeepMemoize } from '@/plugins/useDeepMemoize'; +import FadeTransition from '@/components/elements/transitions/FadeTransition'; export default () => { - const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid); - const databaseLimit = ServerContext.useStoreState((state) => state.server.data!.featureLimits.databases); + const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); + const databaseLimit = ServerContext.useStoreState(state => state.server.data!.featureLimits.databases); const { addError, clearFlashes } = useFlash(); const [loading, setLoading] = useState(true); - const databases = useDeepMemoize(ServerContext.useStoreState((state) => state.databases.data)); - const setDatabases = ServerContext.useStoreActions((state) => state.databases.setDatabases); + const databases = useDeepMemoize(ServerContext.useStoreState(state => state.databases.data)); + const setDatabases = ServerContext.useStoreActions(state => state.databases.setDatabases); useEffect(() => { setLoading(!databases.length); clearFlashes('databases'); getServerDatabases(uuid) - .then((databases) => setDatabases(databases)) - .catch((error) => { + .then(databases => setDatabases(databases)) + .catch(error => { console.error(error); addError({ key: 'databases', message: httpErrorToHuman(error) }); }) @@ -42,7 +42,7 @@ export default () => { {!databases.length && loading ? ( ) : ( - + <> {databases.length > 0 ? ( databases.map((database, index) => ( @@ -73,7 +73,7 @@ export default () => {
- + )} ); diff --git a/resources/scripts/components/server/databases/RotatePasswordButton.tsx b/resources/scripts/components/server/databases/RotatePasswordButton.tsx index 0c687d7763..1159dd87bd 100644 --- a/resources/scripts/components/server/databases/RotatePasswordButton.tsx +++ b/resources/scripts/components/server/databases/RotatePasswordButton.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import { useState } from 'react'; import rotateDatabasePassword from '@/api/server/databases/rotateDatabasePassword'; import { Actions, useStoreActions } from 'easy-peasy'; import { ApplicationStore } from '@/state'; @@ -11,7 +11,7 @@ import tw from 'twin.macro'; export default ({ databaseId, onUpdate }: { databaseId: string; onUpdate: (database: ServerDatabase) => void }) => { const [loading, setLoading] = useState(false); const { addFlash, clearFlashes } = useStoreActions((actions: Actions) => actions.flashes); - const server = ServerContext.useStoreState((state) => state.server.data!); + const server = ServerContext.useStoreState(state => state.server.data!); if (!databaseId) { return null; @@ -22,8 +22,8 @@ export default ({ databaseId, onUpdate }: { databaseId: string; onUpdate: (datab clearFlashes(); rotateDatabasePassword(server.uuid, databaseId) - .then((database) => onUpdate(database)) - .catch((error) => { + .then(database => onUpdate(database)) + .catch(error => { console.error(error); addFlash({ type: 'error', diff --git a/resources/scripts/components/server/features/Features.tsx b/resources/scripts/components/server/features/Features.tsx index 571c7afc6f..720b18853c 100644 --- a/resources/scripts/components/server/features/Features.tsx +++ b/resources/scripts/components/server/features/Features.tsx @@ -1,21 +1,23 @@ -import React, { useMemo } from 'react'; +import type { ComponentType } from 'react'; +import { Suspense, useMemo } from 'react'; + import features from './index'; import { getObjectKeys } from '@/lib/objects'; -type ListItems = [string, React.ComponentType][]; +type ListItems = [string, ComponentType][]; export default ({ enabled }: { enabled: string[] }) => { const mapped: ListItems = useMemo(() => { return getObjectKeys(features) - .filter((key) => enabled.map((v) => v.toLowerCase()).includes(key.toLowerCase())) - .reduce((arr, key) => [...arr, [key, features[key]]], [] as ListItems); + .filter(key => enabled.map(v => v.toLowerCase()).includes(key.toLowerCase())) + .reduce((arr, key) => [...arr, [key, features[key]]] as ListItems, [] as ListItems); }, [enabled]); return ( - + {mapped.map(([key, Component]) => ( ))} - + ); }; diff --git a/resources/scripts/components/server/features/GSLTokenModalFeature.tsx b/resources/scripts/components/server/features/GSLTokenModalFeature.tsx index f56ae7cca4..ec2eead4aa 100644 --- a/resources/scripts/components/server/features/GSLTokenModalFeature.tsx +++ b/resources/scripts/components/server/features/GSLTokenModalFeature.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; import { ServerContext } from '@/state/server'; import Modal from '@/components/elements/Modal'; import tw from 'twin.macro'; @@ -18,10 +18,10 @@ const GSLTokenModalFeature = () => { const [visible, setVisible] = useState(false); const [loading, setLoading] = useState(false); - const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid); - const status = ServerContext.useStoreState((state) => state.status.value); + const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); + const status = ServerContext.useStoreState(state => state.status.value); const { clearFlashes, clearAndAddHttpError } = useFlash(); - const { connected, instance } = ServerContext.useStoreState((state) => state.socket); + const { connected, instance } = ServerContext.useStoreState(state => state.socket); useEffect(() => { if (!connected || !instance || status === 'running') return; @@ -29,7 +29,7 @@ const GSLTokenModalFeature = () => { const errors = ['(gsl token expired)', '(account not found)']; const listener = (line: string) => { - if (errors.some((p) => line.toLowerCase().includes(p))) { + if (errors.some(p => line.toLowerCase().includes(p))) { setVisible(true); } }; @@ -54,7 +54,7 @@ const GSLTokenModalFeature = () => { setLoading(false); setVisible(false); }) - .catch((error) => { + .catch(error => { console.error(error); clearAndAddHttpError({ key: 'feature:gslToken', error }); }) diff --git a/resources/scripts/components/server/features/JavaVersionModalFeature.tsx b/resources/scripts/components/server/features/JavaVersionModalFeature.tsx index 91902f0aa8..80d8cc2871 100644 --- a/resources/scripts/components/server/features/JavaVersionModalFeature.tsx +++ b/resources/scripts/components/server/features/JavaVersionModalFeature.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; import { ServerContext } from '@/state/server'; import Modal from '@/components/elements/Modal'; import tw from 'twin.macro'; @@ -26,25 +26,25 @@ const JavaVersionModalFeature = () => { const [loading, setLoading] = useState(false); const [selectedVersion, setSelectedVersion] = useState(''); - const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid); - const status = ServerContext.useStoreState((state) => state.status.value); + const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); + const status = ServerContext.useStoreState(state => state.status.value); const { clearFlashes, clearAndAddHttpError } = useFlash(); - const { instance } = ServerContext.useStoreState((state) => state.socket); + const { instance } = ServerContext.useStoreState(state => state.socket); - const { data, isValidating, mutate } = getServerStartup(uuid, null, { revalidateOnMount: false }); + const { data, isValidating, mutate } = getServerStartup(uuid, undefined, { revalidateOnMount: false }); useEffect(() => { if (!visible) return; - mutate().then((value) => { + mutate().then(value => { setSelectedVersion(Object.values(value?.dockerImages || [])[0] || ''); }); }, [visible]); - useWebsocketEvent(SocketEvent.CONSOLE_OUTPUT, (data) => { + useWebsocketEvent(SocketEvent.CONSOLE_OUTPUT, data => { if (status === 'running') return; - if (MATCH_ERRORS.some((p) => data.toLowerCase().includes(p.toLowerCase()))) { + if (MATCH_ERRORS.some(p => data.toLowerCase().includes(p.toLowerCase()))) { setVisible(true); } }); @@ -60,7 +60,7 @@ const JavaVersionModalFeature = () => { } setVisible(false); }) - .catch((error) => clearAndAddHttpError({ key: 'feature:javaVersion', error })) + .catch(error => clearAndAddHttpError({ key: 'feature:javaVersion', error })) .then(() => setLoading(false)); }; @@ -86,11 +86,11 @@ const JavaVersionModalFeature = () => {
- setSelectedVersion(e.target.value)}> {!data ? ( diff --git a/resources/scripts/components/server/features/PIDLimitModalFeature.tsx b/resources/scripts/components/server/features/PIDLimitModalFeature.tsx index f4061bc6d9..054f6aaf2f 100644 --- a/resources/scripts/components/server/features/PIDLimitModalFeature.tsx +++ b/resources/scripts/components/server/features/PIDLimitModalFeature.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; import { ServerContext } from '@/state/server'; import Modal from '@/components/elements/Modal'; import tw from 'twin.macro'; @@ -14,10 +14,10 @@ const PIDLimitModalFeature = () => { const [visible, setVisible] = useState(false); const [loading] = useState(false); - const status = ServerContext.useStoreState((state) => state.status.value); + const status = ServerContext.useStoreState(state => state.status.value); const { clearFlashes } = useFlash(); - const { connected, instance } = ServerContext.useStoreState((state) => state.socket); - const isAdmin = useStoreState((state) => state.user.data!.rootAdmin); + const { connected, instance } = ServerContext.useStoreState(state => state.socket); + const isAdmin = useStoreState(state => state.user.data!.rootAdmin); useEffect(() => { if (!connected || !instance || status === 'running') return; @@ -32,7 +32,7 @@ const PIDLimitModalFeature = () => { ]; const listener = (line: string) => { - if (errors.some((p) => line.toLowerCase().includes(p))) { + if (errors.some(p => line.toLowerCase().includes(p))) { setVisible(true); } }; diff --git a/resources/scripts/components/server/features/SteamDiskSpaceFeature.tsx b/resources/scripts/components/server/features/SteamDiskSpaceFeature.tsx index 81acba5796..36aa745633 100644 --- a/resources/scripts/components/server/features/SteamDiskSpaceFeature.tsx +++ b/resources/scripts/components/server/features/SteamDiskSpaceFeature.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; import { ServerContext } from '@/state/server'; import Modal from '@/components/elements/Modal'; import tw from 'twin.macro'; @@ -12,10 +12,10 @@ const SteamDiskSpaceFeature = () => { const [visible, setVisible] = useState(false); const [loading] = useState(false); - const status = ServerContext.useStoreState((state) => state.status.value); + const status = ServerContext.useStoreState(state => state.status.value); const { clearFlashes } = useFlash(); - const { connected, instance } = ServerContext.useStoreState((state) => state.socket); - const isAdmin = useStoreState((state) => state.user.data!.rootAdmin); + const { connected, instance } = ServerContext.useStoreState(state => state.socket); + const isAdmin = useStoreState(state => state.user.data!.rootAdmin); useEffect(() => { if (!connected || !instance || status === 'running') return; @@ -23,7 +23,7 @@ const SteamDiskSpaceFeature = () => { const errors = ['steamcmd needs 250mb of free disk space to update', '0x202 after update job']; const listener = (line: string) => { - if (errors.some((p) => line.toLowerCase().includes(p))) { + if (errors.some(p => line.toLowerCase().includes(p))) { setVisible(true); } }; diff --git a/resources/scripts/components/server/features/eula/EulaModalFeature.tsx b/resources/scripts/components/server/features/eula/EulaModalFeature.tsx index fd7afe1d4a..6fa3ae6ce6 100644 --- a/resources/scripts/components/server/features/eula/EulaModalFeature.tsx +++ b/resources/scripts/components/server/features/eula/EulaModalFeature.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; import { ServerContext } from '@/state/server'; import Modal from '@/components/elements/Modal'; import tw from 'twin.macro'; @@ -12,10 +12,10 @@ const EulaModalFeature = () => { const [visible, setVisible] = useState(false); const [loading, setLoading] = useState(false); - const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid); - const status = ServerContext.useStoreState((state) => state.status.value); + const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); + const status = ServerContext.useStoreState(state => state.status.value); const { clearFlashes, clearAndAddHttpError } = useFlash(); - const { connected, instance } = ServerContext.useStoreState((state) => state.socket); + const { connected, instance } = ServerContext.useStoreState(state => state.socket); useEffect(() => { if (!connected || !instance || status === 'running') return; @@ -46,7 +46,7 @@ const EulaModalFeature = () => { setLoading(false); setVisible(false); }) - .catch((error) => { + .catch(error => { console.error(error); clearAndAddHttpError({ key: 'feature:eula', error }); }) @@ -72,7 +72,7 @@ const EulaModalFeature = () => { target={'_blank'} css={tw`text-primary-300 underline transition-colors duration-150 hover:text-primary-400`} rel={'noreferrer noopener'} - href='https://account.mojang.com/documents/minecraft_eula' + href="https://account.mojang.com/documents/minecraft_eula" > Minecraft® EULA diff --git a/resources/scripts/components/server/files/ChmodFileModal.tsx b/resources/scripts/components/server/files/ChmodFileModal.tsx index 27a474d935..7a0e2fa423 100644 --- a/resources/scripts/components/server/files/ChmodFileModal.tsx +++ b/resources/scripts/components/server/files/ChmodFileModal.tsx @@ -1,6 +1,5 @@ import { fileBitsToString } from '@/helpers'; import useFileManagerSwr from '@/plugins/useFileManagerSwr'; -import React from 'react'; import Modal, { RequiredModalProps } from '@/components/elements/Modal'; import { Form, Formik, FormikHelpers } from 'formik'; import Field from '@/components/elements/Field'; @@ -22,29 +21,29 @@ interface File { type OwnProps = RequiredModalProps & { files: File[] }; const ChmodFileModal = ({ files, ...props }: OwnProps) => { - const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid); + const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); const { mutate } = useFileManagerSwr(); const { clearFlashes, clearAndAddHttpError } = useFlash(); - const directory = ServerContext.useStoreState((state) => state.files.directory); - const setSelectedFiles = ServerContext.useStoreActions((actions) => actions.files.setSelectedFiles); + const directory = ServerContext.useStoreState(state => state.files.directory); + const setSelectedFiles = ServerContext.useStoreActions(actions => actions.files.setSelectedFiles); - const submit = ({ mode }: FormikValues, { setSubmitting }: FormikHelpers) => { + const submit = async ({ mode }: FormikValues, { setSubmitting }: FormikHelpers) => { clearFlashes('files'); - mutate( - (data) => - data.map((f) => - f.name === files[0].file ? { ...f, mode: fileBitsToString(mode, !f.isFile), modeBits: mode } : f + await mutate( + data => + data!.map(f => + f.name === files[0]?.file ? { ...f, mode: fileBitsToString(mode, !f.isFile), modeBits: mode } : f, ), - false + false, ); - const data = files.map((f) => ({ file: f.file, mode: mode })); + const data = files.map(f => ({ file: f.file, mode: mode })); chmodFiles(uuid, directory, data) .then((): Promise => (files.length > 0 ? mutate() : Promise.resolve())) .then(() => setSelectedFiles([])) - .catch((error) => { + .catch(error => { mutate(); setSubmitting(false); clearAndAddHttpError({ key: 'files', error }); @@ -53,7 +52,7 @@ const ChmodFileModal = ({ files, ...props }: OwnProps) => { }; return ( - 1 ? '' : files[0].mode || '' }}> + 1 ? '' : files[0]?.mode ?? '' }}> {({ isSubmitting }) => (
diff --git a/resources/scripts/components/server/files/FileDropdownMenu.tsx b/resources/scripts/components/server/files/FileDropdownMenu.tsx index fd892951db..41c446919c 100644 --- a/resources/scripts/components/server/files/FileDropdownMenu.tsx +++ b/resources/scripts/components/server/files/FileDropdownMenu.tsx @@ -1,4 +1,5 @@ -import React, { memo, useRef, useState } from 'react'; +import { memo, useRef, useState } from 'react'; +import * as React from 'react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faBoxOpen, @@ -14,7 +15,7 @@ import { } from '@fortawesome/free-solid-svg-icons'; import RenameFileModal from '@/components/server/files/RenameFileModal'; import { ServerContext } from '@/state/server'; -import { join } from 'path'; +import { join } from 'pathe'; import deleteFiles from '@/api/server/files/deleteFiles'; import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; import copyFile from '@/api/server/files/copyFile'; @@ -25,7 +26,7 @@ import tw from 'twin.macro'; import { FileObject } from '@/api/server/files/loadDirectory'; import useFileManagerSwr from '@/plugins/useFileManagerSwr'; import DropdownMenu from '@/components/elements/DropdownMenu'; -import styled from 'styled-components/macro'; +import styled from 'styled-components'; import useEventListener from '@/plugins/useEventListener'; import compressFiles from '@/api/server/files/compressFiles'; import decompressFiles from '@/api/server/files/decompressFiles'; @@ -37,7 +38,7 @@ type ModalType = 'rename' | 'move' | 'chmod'; const StyledRow = styled.div<{ $danger?: boolean }>` ${tw`p-2 flex items-center rounded`}; - ${(props) => + ${props => props.$danger ? tw`hover:bg-red-100 hover:text-red-700` : tw`hover:bg-neutral-100 hover:text-neutral-700`}; `; @@ -60,10 +61,10 @@ const FileDropdownMenu = ({ file }: { file: FileObject }) => { const [modal, setModal] = useState(null); const [showConfirmation, setShowConfirmation] = useState(false); - const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid); + const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); const { mutate } = useFileManagerSwr(); const { clearAndAddHttpError, clearFlashes } = useFlash(); - const directory = ServerContext.useStoreState((state) => state.files.directory); + const directory = ServerContext.useStoreState(state => state.files.directory); useEventListener(`pterodactyl:files:ctx:${file.key}`, (e: CustomEvent) => { if (onClickRef.current) { @@ -71,14 +72,14 @@ const FileDropdownMenu = ({ file }: { file: FileObject }) => { } }); - const doDeletion = () => { + const doDeletion = async () => { clearFlashes('files'); // For UI speed, immediately remove the file from the listing before calling the deletion function. // If the delete actually fails, we'll fetch the current directory contents again automatically. - mutate((files) => files.filter((f) => f.key !== file.key), false); + await mutate(files => files!.filter(f => f.key !== file.key), false); - deleteFiles(uuid, directory, [file.name]).catch((error) => { + deleteFiles(uuid, directory, [file.name]).catch(error => { mutate(); clearAndAddHttpError({ key: 'files', error }); }); @@ -90,7 +91,7 @@ const FileDropdownMenu = ({ file }: { file: FileObject }) => { copyFile(uuid, join(directory, file.name)) .then(() => mutate()) - .catch((error) => clearAndAddHttpError({ key: 'files', error })) + .catch(error => clearAndAddHttpError({ key: 'files', error })) .then(() => setShowSpinner(false)); }; @@ -99,11 +100,11 @@ const FileDropdownMenu = ({ file }: { file: FileObject }) => { clearFlashes('files'); getFileDownloadUrl(uuid, join(directory, file.name)) - .then((url) => { + .then(url => { // @ts-expect-error this is valid window.location = url; }) - .catch((error) => clearAndAddHttpError({ key: 'files', error })) + .catch(error => clearAndAddHttpError({ key: 'files', error })) .then(() => setShowSpinner(false)); }; @@ -113,7 +114,7 @@ const FileDropdownMenu = ({ file }: { file: FileObject }) => { compressFiles(uuid, directory, [file.name]) .then(() => mutate()) - .catch((error) => clearAndAddHttpError({ key: 'files', error })) + .catch(error => clearAndAddHttpError({ key: 'files', error })) .then(() => setShowSpinner(false)); }; @@ -123,7 +124,7 @@ const FileDropdownMenu = ({ file }: { file: FileObject }) => { decompressFiles(uuid, directory, file.name) .then(() => mutate()) - .catch((error) => clearAndAddHttpError({ key: 'files', error })) + .catch(error => clearAndAddHttpError({ key: 'files', error })) .then(() => setShowSpinner(false)); }; @@ -141,7 +142,7 @@ const FileDropdownMenu = ({ file }: { file: FileObject }) => { ( + renderToggle={onClick => (
{modal ? ( diff --git a/resources/scripts/components/server/files/FileEditContainer.tsx b/resources/scripts/components/server/files/FileEditContainer.tsx index 28ba91d255..623c130c92 100644 --- a/resources/scripts/components/server/files/FileEditContainer.tsx +++ b/resources/scripts/components/server/files/FileEditContainer.tsx @@ -1,25 +1,27 @@ -import React, { useEffect, useState } from 'react'; -import getFileContents from '@/api/server/files/getFileContents'; +import type { LanguageDescription } from '@codemirror/language'; +import { dirname } from 'pathe'; +import { useEffect, useState } from 'react'; +import { useLocation, useNavigate, useParams } from 'react-router-dom'; +import tw from 'twin.macro'; + import { httpErrorToHuman } from '@/api/http'; -import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; +import getFileContents from '@/api/server/files/getFileContents'; import saveFileContents from '@/api/server/files/saveFileContents'; -import FileManagerBreadcrumbs from '@/components/server/files/FileManagerBreadcrumbs'; -import { useHistory, useLocation, useParams } from 'react-router'; -import FileNameModal from '@/components/server/files/FileNameModal'; -import Can from '@/components/elements/Can'; import FlashMessageRender from '@/components/FlashMessageRender'; -import PageContentBlock from '@/components/elements/PageContentBlock'; -import { ServerError } from '@/components/elements/ScreenBlock'; -import tw from 'twin.macro'; import Button from '@/components/elements/Button'; +import Can from '@/components/elements/Can'; import Select from '@/components/elements/Select'; +import PageContentBlock from '@/components/elements/PageContentBlock'; +import { ServerError } from '@/components/elements/ScreenBlock'; +import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; +import FileManagerBreadcrumbs from '@/components/server/files/FileManagerBreadcrumbs'; +import FileNameModal from '@/components/server/files/FileNameModal'; +import ErrorBoundary from '@/components/elements/ErrorBoundary'; +import { Editor } from '@/components/elements/editor'; import modes from '@/modes'; import useFlash from '@/plugins/useFlash'; import { ServerContext } from '@/state/server'; -import ErrorBoundary from '@/components/elements/ErrorBoundary'; import { encodePathSegments, hashToPath } from '@/helpers'; -import { dirname } from 'path'; -import CodemirrorEditor from '@/components/elements/CodemirrorEditor'; export default () => { const [error, setError] = useState(''); @@ -28,13 +30,14 @@ export default () => { const [content, setContent] = useState(''); const [modalVisible, setModalVisible] = useState(false); const [mode, setMode] = useState('text/plain'); + const [language, setLanguage] = useState(); - const history = useHistory(); const { hash } = useLocation(); + const navigate = useNavigate(); - const id = ServerContext.useStoreState((state) => state.server.data!.id); - const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid); - const setDirectory = ServerContext.useStoreActions((actions) => actions.files.setDirectory); + const id = ServerContext.useStoreState(state => state.server.data!.id); + const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); + const setDirectory = ServerContext.useStoreActions(actions => actions.files.setDirectory); const { addError, clearFlashes } = useFlash(); let fetchFileContent: null | (() => Promise) = null; @@ -48,7 +51,7 @@ export default () => { setDirectory(dirname(path)); getFileContents(uuid, path) .then(setContent) - .catch((error) => { + .catch(error => { console.error(error); setError(httpErrorToHuman(error)); }) @@ -63,16 +66,16 @@ export default () => { setLoading(true); clearFlashes('files:view'); fetchFileContent() - .then((content) => saveFileContents(uuid, name || hashToPath(hash), content)) + .then(content => saveFileContents(uuid, name || hashToPath(hash), content)) .then(() => { if (name) { - history.push(`/server/${id}/files/edit#/${encodePathSegments(name)}`); + navigate(`/server/${id}/files/edit#/${encodePathSegments(name)}`); return; } return Promise.resolve(); }) - .catch((error) => { + .catch(error => { console.error(error); addError({ message: httpErrorToHuman(error), key: 'files:view' }); }) @@ -80,17 +83,20 @@ export default () => { }; if (error) { - return history.goBack()} />; + // TODO: onBack + return ; } return ( +
+ {hash.replace(/^#/, '').endsWith('.pteroignore') && (

@@ -102,22 +108,24 @@ export default () => {

)} + setModalVisible(false)} - onFileNamed={(name) => { + onFileNamed={name => { setModalVisible(false); save(name); }} /> +
- { + language={language} + onLanguageChanged={setLanguage} + fetchContent={value => { fetchFileContent = value; }} onContentSaved={() => { @@ -129,16 +137,18 @@ export default () => { }} />
+
- setMode(e.currentTarget.value)}> + {modes.map(mode => ( ))}
+ {action === 'edit' ? (
); diff --git a/resources/scripts/components/server/files/FileManagerContainer.tsx b/resources/scripts/components/server/files/FileManagerContainer.tsx index 3bebd20625..d699cd7833 100644 --- a/resources/scripts/components/server/files/FileManagerContainer.tsx +++ b/resources/scripts/components/server/files/FileManagerContainer.tsx @@ -1,6 +1,8 @@ -import React, { useEffect } from 'react'; +import type { ChangeEvent } from 'react'; +import { useEffect } from 'react'; +import tw from 'twin.macro'; + import { httpErrorToHuman } from '@/api/http'; -import { CSSTransition } from 'react-transition-group'; import Spinner from '@/components/elements/Spinner'; import FileObjectRow from '@/components/server/files/FileObjectRow'; import FileManagerBreadcrumbs from '@/components/server/files/FileManagerBreadcrumbs'; @@ -9,7 +11,6 @@ import NewDirectoryButton from '@/components/server/files/NewDirectoryButton'; import { NavLink, useLocation } from 'react-router-dom'; import Can from '@/components/elements/Can'; import { ServerError } from '@/components/elements/ScreenBlock'; -import tw from 'twin.macro'; import { Button } from '@/components/elements/button/index'; import { ServerContext } from '@/state/server'; import useFileManagerSwr from '@/plugins/useFileManagerSwr'; @@ -22,24 +23,25 @@ import ErrorBoundary from '@/components/elements/ErrorBoundary'; import { FileActionCheckbox } from '@/components/server/files/SelectFileCheckbox'; import { hashToPath } from '@/helpers'; import style from './style.module.css'; +import FadeTransition from '@/components/elements/transitions/FadeTransition'; const sortFiles = (files: FileObject[]): FileObject[] => { const sortedFiles: FileObject[] = files .sort((a, b) => a.name.localeCompare(b.name)) .sort((a, b) => (a.isFile === b.isFile ? 0 : a.isFile ? 1 : -1)); - return sortedFiles.filter((file, index) => index === 0 || file.name !== sortedFiles[index - 1].name); + return sortedFiles.filter((file, index) => index === 0 || file.name !== sortedFiles[index - 1]?.name); }; export default () => { - const id = ServerContext.useStoreState((state) => state.server.data!.id); + const id = ServerContext.useStoreState(state => state.server.data!.id); const { hash } = useLocation(); const { data: files, error, mutate } = useFileManagerSwr(); - const directory = ServerContext.useStoreState((state) => state.files.directory); - const clearFlashes = useStoreActions((actions) => actions.flashes.clearFlashes); - const setDirectory = ServerContext.useStoreActions((actions) => actions.files.setDirectory); + const directory = ServerContext.useStoreState(state => state.files.directory); + const clearFlashes = useStoreActions(actions => actions.flashes.clearFlashes); + const setDirectory = ServerContext.useStoreActions(actions => actions.files.setDirectory); - const setSelectedFiles = ServerContext.useStoreActions((actions) => actions.files.setSelectedFiles); - const selectedFilesLength = ServerContext.useStoreState((state) => state.files.selectedFiles.length); + const setSelectedFiles = ServerContext.useStoreActions(actions => actions.files.setSelectedFiles); + const selectedFilesLength = ServerContext.useStoreState(state => state.files.selectedFiles.length); useEffect(() => { clearFlashes('files'); @@ -48,11 +50,11 @@ export default () => { }, [hash]); useEffect(() => { - mutate(); + void mutate(); }, [directory]); - const onSelectAllClick = (e: React.ChangeEvent) => { - setSelectedFiles(e.currentTarget.checked ? files?.map((file) => file.name) || [] : []); + const onSelectAllClick = (e: ChangeEvent) => { + setSelectedFiles(e.currentTarget.checked ? files?.map(file => file.name) || [] : []); }; if (error) { @@ -92,7 +94,7 @@ export default () => { {!files.length ? (

This directory seems to be empty.

) : ( - +
{files.length > 250 && (
@@ -102,12 +104,12 @@ export default () => {

)} - {sortFiles(files.slice(0, 250)).map((file) => ( + {sortFiles(files.slice(0, 250)).map(file => ( ))}
-
+ )} )} diff --git a/resources/scripts/components/server/files/FileManagerStatus.tsx b/resources/scripts/components/server/files/FileManagerStatus.tsx index 620e067c17..2e1534b416 100644 --- a/resources/scripts/components/server/files/FileManagerStatus.tsx +++ b/resources/scripts/components/server/files/FileManagerStatus.tsx @@ -1,12 +1,13 @@ -import React, { useContext, useEffect } from 'react'; -import { ServerContext } from '@/state/server'; import { CloudUploadIcon, XIcon } from '@heroicons/react/solid'; -import asDialog from '@/hoc/asDialog'; -import { Dialog, DialogWrapperContext } from '@/components/elements/dialog'; +import { useSignal } from '@preact/signals-react'; +import { useContext, useEffect } from 'react'; + import { Button } from '@/components/elements/button/index'; +import { Dialog, DialogWrapperContext } from '@/components/elements/dialog'; import Tooltip from '@/components/elements/tooltip/Tooltip'; import Code from '@/components/elements/Code'; -import { useSignal } from '@preact/signals-react'; +import asDialog from '@/hoc/asDialog'; +import { ServerContext } from '@/state/server'; const svgProps = { cx: 16, @@ -32,10 +33,10 @@ const Spinner = ({ progress, className }: { progress: number; className?: string const FileUploadList = () => { const { close } = useContext(DialogWrapperContext); - const removeFileUpload = ServerContext.useStoreActions((actions) => actions.files.removeFileUpload); - const clearFileUploads = ServerContext.useStoreActions((actions) => actions.files.clearFileUploads); - const uploads = ServerContext.useStoreState((state) => - Object.entries(state.files.uploads).sort(([a], [b]) => a.localeCompare(b)) + const removeFileUpload = ServerContext.useStoreActions(actions => actions.files.removeFileUpload); + const clearFileUploads = ServerContext.useStoreActions(actions => actions.files.clearFileUploads); + const uploads = ServerContext.useStoreState(state => + Object.entries(state.files.uploads).sort(([a], [b]) => a.localeCompare(b)), ); return ( @@ -74,8 +75,8 @@ const FileUploadListDialog = asDialog({ export default () => { const open = useSignal(false); - const count = ServerContext.useStoreState((state) => Object.keys(state.files.uploads).length); - const progress = ServerContext.useStoreState((state) => ({ + const count = ServerContext.useStoreState(state => Object.keys(state.files.uploads).length); + const progress = ServerContext.useStoreState(state => ({ uploaded: Object.values(state.files.uploads).reduce((count, file) => count + file.loaded, 0), total: Object.values(state.files.uploads).reduce((count, file) => count + file.total, 0), })); diff --git a/resources/scripts/components/server/files/FileNameModal.tsx b/resources/scripts/components/server/files/FileNameModal.tsx index 04781babec..adb6a75556 100644 --- a/resources/scripts/components/server/files/FileNameModal.tsx +++ b/resources/scripts/components/server/files/FileNameModal.tsx @@ -1,10 +1,9 @@ -import React from 'react'; import Modal, { RequiredModalProps } from '@/components/elements/Modal'; import { Form, Formik, FormikHelpers } from 'formik'; import { object, string } from 'yup'; import Field from '@/components/elements/Field'; import { ServerContext } from '@/state/server'; -import { join } from 'path'; +import { join } from 'pathe'; import tw from 'twin.macro'; import Button from '@/components/elements/Button'; @@ -17,7 +16,7 @@ interface Values { } export default ({ onFileNamed, onDismissed, ...props }: Props) => { - const directory = ServerContext.useStoreState((state) => state.files.directory); + const directory = ServerContext.useStoreState(state => state.files.directory); const submit = (values: Values, { setSubmitting }: FormikHelpers) => { onFileNamed(join(directory, values.fileName)); diff --git a/resources/scripts/components/server/files/FileObjectRow.tsx b/resources/scripts/components/server/files/FileObjectRow.tsx index 8032dab1e4..d945c2a3af 100644 --- a/resources/scripts/components/server/files/FileObjectRow.tsx +++ b/resources/scripts/components/server/files/FileObjectRow.tsx @@ -1,69 +1,74 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faFileAlt, faFileArchive, faFileImport, faFolder } from '@fortawesome/free-solid-svg-icons'; -import { encodePathSegments } from '@/helpers'; import { differenceInHours, format, formatDistanceToNow } from 'date-fns'; -import React, { memo } from 'react'; +import type { ReactNode } from 'react'; +import { memo } from 'react'; +import isEqual from 'react-fast-compare'; +import { NavLink } from 'react-router-dom'; +import tw from 'twin.macro'; +import { join } from 'pathe'; + +import { encodePathSegments } from '@/helpers'; import { FileObject } from '@/api/server/files/loadDirectory'; import FileDropdownMenu from '@/components/server/files/FileDropdownMenu'; -import { ServerContext } from '@/state/server'; -import { NavLink, useRouteMatch } from 'react-router-dom'; -import tw from 'twin.macro'; -import isEqual from 'react-fast-compare'; import SelectFileCheckbox from '@/components/server/files/SelectFileCheckbox'; -import { usePermissions } from '@/plugins/usePermissions'; -import { join } from 'path'; import { bytesToString } from '@/lib/formatters'; +import { usePermissions } from '@/plugins/usePermissions'; +import { ServerContext } from '@/state/server'; import styles from './style.module.css'; -const Clickable: React.FC<{ file: FileObject }> = memo(({ file, children }) => { +function Clickable({ file, children }: { file: FileObject; children: ReactNode }) { const [canReadContents] = usePermissions(['file.read-content']); - const directory = ServerContext.useStoreState((state) => state.files.directory); - - const match = useRouteMatch(); + const id = ServerContext.useStoreState(state => state.server.data!.id); + const directory = ServerContext.useStoreState(state => state.files.directory); return !canReadContents || (file.isFile && !file.isEditable()) ? (
{children}
) : ( {children} ); -}, isEqual); +} -const FileObjectRow = ({ file }: { file: FileObject }) => ( -
{ - e.preventDefault(); - window.dispatchEvent(new CustomEvent(`pterodactyl:files:ctx:${file.key}`, { detail: e.clientX })); - }} - > - - -
- {file.isFile ? ( - - ) : ( - - )} -
-
{file.name}
- {file.isFile && } - -
- -
-); +const MemoizedClickable = memo(Clickable, isEqual); + +function FileObjectRow({ file }: { file: FileObject }) { + return ( +
{ + e.preventDefault(); + window.dispatchEvent(new CustomEvent(`pterodactyl:files:ctx:${file.key}`, { detail: e.clientX })); + }} + > + + +
+ {file.isFile ? ( + + ) : ( + + )} +
+
{file.name}
+ {file.isFile && } + +
+ +
+ ); +} export default memo(FileObjectRow, (prevProps, nextProps) => { /* eslint-disable @typescript-eslint/no-unused-vars */ diff --git a/resources/scripts/components/server/files/MassActionsBar.tsx b/resources/scripts/components/server/files/MassActionsBar.tsx index 44230f2144..ff7c8eb6fb 100644 --- a/resources/scripts/components/server/files/MassActionsBar.tsx +++ b/resources/scripts/components/server/files/MassActionsBar.tsx @@ -1,19 +1,19 @@ -import React, { useEffect, useState } from 'react'; -import tw from 'twin.macro'; -import { Button } from '@/components/elements/button/index'; -import Fade from '@/components/elements/Fade'; +import { useEffect, useState } from 'react'; + +import compressFiles from '@/api/server/files/compressFiles'; +import deleteFiles from '@/api/server/files/deleteFiles'; +import { Button } from '@/components/elements/button'; +import { Dialog } from '@/components/elements/dialog'; +import Portal from '@/components/elements/Portal'; import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; +import RenameFileModal from '@/components/server/files/RenameFileModal'; import useFileManagerSwr from '@/plugins/useFileManagerSwr'; import useFlash from '@/plugins/useFlash'; -import compressFiles from '@/api/server/files/compressFiles'; import { ServerContext } from '@/state/server'; -import deleteFiles from '@/api/server/files/deleteFiles'; -import RenameFileModal from '@/components/server/files/RenameFileModal'; -import Portal from '@/components/elements/Portal'; -import { Dialog } from '@/components/elements/dialog'; +import FadeTransition from '@/components/elements/transitions/FadeTransition'; const MassActionsBar = () => { - const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid); + const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); const { mutate } = useFileManagerSwr(); const { clearFlashes, clearAndAddHttpError } = useFlash(); @@ -21,10 +21,10 @@ const MassActionsBar = () => { const [loadingMessage, setLoadingMessage] = useState(''); const [showConfirm, setShowConfirm] = useState(false); const [showMove, setShowMove] = useState(false); - const directory = ServerContext.useStoreState((state) => state.files.directory); + const directory = ServerContext.useStoreState(state => state.files.directory); - const selectedFiles = ServerContext.useStoreState((state) => state.files.selectedFiles); - const setSelectedFiles = ServerContext.useStoreActions((actions) => actions.files.setSelectedFiles); + const selectedFiles = ServerContext.useStoreState(state => state.files.selectedFiles); + const setSelectedFiles = ServerContext.useStoreActions(actions => actions.files.setSelectedFiles); useEffect(() => { if (!loading) setLoadingMessage(''); @@ -38,7 +38,7 @@ const MassActionsBar = () => { compressFiles(uuid, directory, selectedFiles) .then(() => mutate()) .then(() => setSelectedFiles([])) - .catch((error) => clearAndAddHttpError({ key: 'files', error })) + .catch(error => clearAndAddHttpError({ key: 'files', error })) .then(() => setLoading(false)); }; @@ -49,12 +49,12 @@ const MassActionsBar = () => { setLoadingMessage('Deleting files...'); deleteFiles(uuid, directory, selectedFiles) - .then(() => { - mutate((files) => files.filter((f) => selectedFiles.indexOf(f.name) < 0), false); + .then(async () => { + await mutate(files => files!.filter(f => selectedFiles.indexOf(f.name) < 0), false); setSelectedFiles([]); }) - .catch((error) => { - mutate(); + .catch(async error => { + await mutate(); clearAndAddHttpError({ key: 'files', error }); }) .then(() => setLoading(false)); @@ -62,7 +62,7 @@ const MassActionsBar = () => { return ( <> -
+
{loadingMessage} @@ -73,12 +73,12 @@ const MassActionsBar = () => { onClose={() => setShowConfirm(false)} onConfirmed={onClickConfirmDeletion} > -

+

Are you sure you want to delete  - {selectedFiles.length} files? This is a + {selectedFiles.length} files? This is a permanent action and the files cannot be recovered.

- {selectedFiles.slice(0, 15).map((file) => ( + {selectedFiles.slice(0, 15).map(file => (
  • {file}
  • ))} {selectedFiles.length > 15 &&
  • and {selectedFiles.length - 15} others
  • } @@ -93,16 +93,16 @@ const MassActionsBar = () => { /> )} -
    - 0} unmountOnExit> -
    +
    + 0} appear unmount> +
    setShowConfirm(true)}> Delete
    - +
    diff --git a/resources/scripts/components/server/files/NewDirectoryButton.tsx b/resources/scripts/components/server/files/NewDirectoryButton.tsx index ff74357cf4..f92c6f602d 100644 --- a/resources/scripts/components/server/files/NewDirectoryButton.tsx +++ b/resources/scripts/components/server/files/NewDirectoryButton.tsx @@ -1,8 +1,8 @@ -import React, { useContext, useEffect, useState } from 'react'; +import { useContext, useEffect, useState } from 'react'; import { ServerContext } from '@/state/server'; import { Form, Formik, FormikHelpers } from 'formik'; import Field from '@/components/elements/Field'; -import { join } from 'path'; +import { join } from 'pathe'; import { object, string } from 'yup'; import createDirectory from '@/api/server/files/createDirectory'; import tw from 'twin.macro'; @@ -42,8 +42,8 @@ const generateDirectoryData = (name: string): FileObject => ({ const NewDirectoryDialog = asDialog({ title: 'Create Directory', })(() => { - const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid); - const directory = ServerContext.useStoreState((state) => state.files.directory); + const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); + const directory = ServerContext.useStoreState(state => state.files.directory); const { mutate } = useFileManagerSwr(); const { close } = useContext(DialogWrapperContext); @@ -57,9 +57,9 @@ const NewDirectoryDialog = asDialog({ const submit = ({ directoryName }: Values, { setSubmitting }: FormikHelpers) => { createDirectory(uuid, directory, directoryName) - .then(() => mutate((data) => [...data, generateDirectoryData(directoryName)], false)) + .then(() => mutate(data => [...data!, generateDirectoryData(directoryName)], false)) .then(() => close()) - .catch((error) => { + .catch(error => { setSubmitting(false); clearAndAddHttpError(error); }); diff --git a/resources/scripts/components/server/files/RenameFileModal.tsx b/resources/scripts/components/server/files/RenameFileModal.tsx index 4f675338e1..7b72811e99 100644 --- a/resources/scripts/components/server/files/RenameFileModal.tsx +++ b/resources/scripts/components/server/files/RenameFileModal.tsx @@ -1,8 +1,7 @@ -import React from 'react'; import Modal, { RequiredModalProps } from '@/components/elements/Modal'; import { Form, Formik, FormikHelpers } from 'formik'; import Field from '@/components/elements/Field'; -import { join } from 'path'; +import { join } from 'pathe'; import renameFiles from '@/api/server/files/renameFiles'; import { ServerContext } from '@/state/server'; import tw from 'twin.macro'; @@ -17,11 +16,11 @@ interface FormikValues { type OwnProps = RequiredModalProps & { files: string[]; useMoveTerminology?: boolean }; const RenameFileModal = ({ files, useMoveTerminology, ...props }: OwnProps) => { - const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid); + const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); const { mutate } = useFileManagerSwr(); const { clearFlashes, clearAndAddHttpError } = useFlash(); - const directory = ServerContext.useStoreState((state) => state.files.directory); - const setSelectedFiles = ServerContext.useStoreActions((actions) => actions.files.setSelectedFiles); + const directory = ServerContext.useStoreState(state => state.files.directory); + const setSelectedFiles = ServerContext.useStoreActions(actions => actions.files.setSelectedFiles); const submit = ({ name }: FormikValues, { setSubmitting }: FormikHelpers) => { clearFlashes('files'); @@ -30,24 +29,24 @@ const RenameFileModal = ({ files, useMoveTerminology, ...props }: OwnProps) => { if (files.length === 1) { if (!useMoveTerminology && len === 1) { // Rename the file within this directory. - mutate((data) => data.map((f) => (f.name === files[0] ? { ...f, name } : f)), false); + mutate(data => data!.map(f => (f.name === files[0] ? { ...f, name } : f)), false); } else if (useMoveTerminology || len > 1) { // Remove the file from this directory since they moved it elsewhere. - mutate((data) => data.filter((f) => f.name !== files[0]), false); + mutate(data => data!.filter(f => f.name !== files[0]), false); } } let data; if (useMoveTerminology && files.length > 1) { - data = files.map((f) => ({ from: f, to: join(name, f) })); + data = files.map(f => ({ from: f, to: join(name, f) })); } else { - data = files.map((f) => ({ from: f, to: name })); + data = files.map(f => ({ from: f, to: name })); } renameFiles(uuid, directory, data) .then((): Promise => (files.length > 0 ? mutate() : Promise.resolve())) .then(() => setSelectedFiles([])) - .catch((error) => { + .catch(error => { mutate(); setSubmitting(false); clearAndAddHttpError({ key: 'files', error }); diff --git a/resources/scripts/components/server/files/SelectFileCheckbox.tsx b/resources/scripts/components/server/files/SelectFileCheckbox.tsx index 1708fd4132..cc9807c2dd 100644 --- a/resources/scripts/components/server/files/SelectFileCheckbox.tsx +++ b/resources/scripts/components/server/files/SelectFileCheckbox.tsx @@ -1,7 +1,7 @@ -import React from 'react'; +import * as React from 'react'; import tw from 'twin.macro'; import { ServerContext } from '@/state/server'; -import styled from 'styled-components/macro'; +import styled from 'styled-components'; import Input from '@/components/elements/Input'; export const FileActionCheckbox = styled(Input)` @@ -15,9 +15,9 @@ export const FileActionCheckbox = styled(Input)` `; export default ({ name }: { name: string }) => { - const isChecked = ServerContext.useStoreState((state) => state.files.selectedFiles.indexOf(name) >= 0); - const appendSelectedFile = ServerContext.useStoreActions((actions) => actions.files.appendSelectedFile); - const removeSelectedFile = ServerContext.useStoreActions((actions) => actions.files.removeSelectedFile); + const isChecked = ServerContext.useStoreState(state => state.files.selectedFiles.indexOf(name) >= 0); + const appendSelectedFile = ServerContext.useStoreActions(actions => actions.files.appendSelectedFile); + const removeSelectedFile = ServerContext.useStoreActions(actions => actions.files.removeSelectedFile); return (
    - + { + onChange={e => { if (!e.currentTarget.files) return; onFileSubmission(e.currentTarget.files); diff --git a/resources/scripts/components/server/network/AllocationRow.tsx b/resources/scripts/components/server/network/AllocationRow.tsx index e68bc3359f..079c4d3507 100644 --- a/resources/scripts/components/server/network/AllocationRow.tsx +++ b/resources/scripts/components/server/network/AllocationRow.tsx @@ -1,4 +1,4 @@ -import React, { memo, useCallback, useState } from 'react'; +import { memo, useCallback, useState } from 'react'; import isEqual from 'react-fast-compare'; import tw from 'twin.macro'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; @@ -9,7 +9,7 @@ import Can from '@/components/elements/Can'; import { Button } from '@/components/elements/button/index'; import GreyRowBox from '@/components/elements/GreyRowBox'; import { Allocation } from '@/api/server/getServer'; -import styled from 'styled-components/macro'; +import styled from 'styled-components'; import { debounce } from 'debounce'; import setServerAllocationNotes from '@/api/server/network/setServerAllocationNotes'; import { useFlashKey } from '@/plugins/useFlash'; @@ -32,11 +32,11 @@ interface Props { const AllocationRow = ({ allocation }: Props) => { const [loading, setLoading] = useState(false); const { clearFlashes, clearAndAddHttpError } = useFlashKey('server:network'); - const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid); + const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); const { mutate } = getServerAllocations(); const onNotesChanged = useCallback((id: number, notes: string) => { - mutate((data) => data?.map((a) => (a.id === id ? { ...a, notes } : a)), false); + mutate(data => data?.map(a => (a.id === id ? { ...a, notes } : a)), false); }, []); const setAllocationNotes = debounce((notes: string) => { @@ -45,15 +45,15 @@ const AllocationRow = ({ allocation }: Props) => { setServerAllocationNotes(uuid, allocation.id, notes) .then(() => onNotesChanged(allocation.id, notes)) - .catch((error) => clearAndAddHttpError(error)) + .catch(error => clearAndAddHttpError(error)) .then(() => setLoading(false)); }, 750); const setPrimaryAllocation = () => { clearFlashes(); - mutate((data) => data?.map((a) => ({ ...a, isDefault: a.id === allocation.id })), false); + mutate(data => data?.map(a => ({ ...a, isDefault: a.id === allocation.id })), false); - setPrimaryServerAllocation(uuid, allocation.id).catch((error) => { + setPrimaryServerAllocation(uuid, allocation.id).catch(error => { clearAndAddHttpError(error); mutate(); }); @@ -90,7 +90,7 @@ const AllocationRow = ({ allocation }: Props) => { className={'bg-neutral-800 hover:border-neutral-600 border-transparent'} placeholder={'Notes'} defaultValue={allocation.notes || undefined} - onChange={(e) => setAllocationNotes(e.currentTarget.value)} + onChange={e => setAllocationNotes(e.currentTarget.value)} />
    diff --git a/resources/scripts/components/server/network/DeleteAllocationButton.tsx b/resources/scripts/components/server/network/DeleteAllocationButton.tsx index c6804484e0..5dd5b7c656 100644 --- a/resources/scripts/components/server/network/DeleteAllocationButton.tsx +++ b/resources/scripts/components/server/network/DeleteAllocationButton.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import { useState } from 'react'; import { faTrashAlt } from '@fortawesome/free-solid-svg-icons'; import tw from 'twin.macro'; import Icon from '@/components/elements/Icon'; @@ -16,8 +16,8 @@ interface Props { const DeleteAllocationButton = ({ allocation }: Props) => { const [confirm, setConfirm] = useState(false); - const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid); - const setServerFromState = ServerContext.useStoreActions((actions) => actions.server.setServerFromState); + const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); + const setServerFromState = ServerContext.useStoreActions(actions => actions.server.setServerFromState); const { mutate } = getServerAllocations(); const { clearFlashes, clearAndAddHttpError } = useFlashKey('server:network'); @@ -25,10 +25,10 @@ const DeleteAllocationButton = ({ allocation }: Props) => { const deleteAllocation = () => { clearFlashes(); - mutate((data) => data?.filter((a) => a.id !== allocation), false); - setServerFromState((s) => ({ ...s, allocations: s.allocations.filter((a) => a.id !== allocation) })); + mutate(data => data?.filter(a => a.id !== allocation), false); + setServerFromState(s => ({ ...s, allocations: s.allocations.filter(a => a.id !== allocation) })); - deleteServerAllocation(uuid, allocation).catch((error) => { + deleteServerAllocation(uuid, allocation).catch(error => { clearAndAddHttpError(error); mutate(); }); diff --git a/resources/scripts/components/server/network/NetworkContainer.tsx b/resources/scripts/components/server/network/NetworkContainer.tsx index 182beaab8a..5f4acce246 100644 --- a/resources/scripts/components/server/network/NetworkContainer.tsx +++ b/resources/scripts/components/server/network/NetworkContainer.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; import Spinner from '@/components/elements/Spinner'; import { useFlashKey } from '@/plugins/useFlash'; import ServerContentBlock from '@/components/elements/ServerContentBlock'; @@ -15,10 +15,10 @@ import { useDeepCompareEffect } from '@/plugins/useDeepCompareEffect'; const NetworkContainer = () => { const [loading, setLoading] = useState(false); - const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid); - const allocationLimit = ServerContext.useStoreState((state) => state.server.data!.featureLimits.allocations); - const allocations = ServerContext.useStoreState((state) => state.server.data!.allocations, isEqual); - const setServerFromState = ServerContext.useStoreActions((actions) => actions.server.setServerFromState); + const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); + const allocationLimit = ServerContext.useStoreState(state => state.server.data!.featureLimits.allocations); + const allocations = ServerContext.useStoreState(state => state.server.data!.allocations, isEqual); + const setServerFromState = ServerContext.useStoreActions(actions => actions.server.setServerFromState); const { clearFlashes, clearAndAddHttpError } = useFlashKey('server:network'); const { data, error, mutate } = getServerAllocations(); @@ -34,7 +34,7 @@ const NetworkContainer = () => { useDeepCompareEffect(() => { if (!data) return; - setServerFromState((state) => ({ ...state, allocations: data })); + setServerFromState(state => ({ ...state, allocations: data })); }, [data]); const onCreateAllocation = () => { @@ -42,11 +42,11 @@ const NetworkContainer = () => { setLoading(true); createServerAllocation(uuid) - .then((allocation) => { - setServerFromState((s) => ({ ...s, allocations: s.allocations.concat(allocation) })); + .then(allocation => { + setServerFromState(s => ({ ...s, allocations: s.allocations.concat(allocation) })); return mutate(data?.concat(allocation), false); }) - .catch((error) => clearAndAddHttpError(error)) + .catch(error => clearAndAddHttpError(error)) .then(() => setLoading(false)); }; @@ -56,7 +56,7 @@ const NetworkContainer = () => { ) : ( <> - {data.map((allocation) => ( + {data.map(allocation => ( ))} {allocationLimit > 0 && ( diff --git a/resources/scripts/components/server/schedules/DeleteScheduleButton.tsx b/resources/scripts/components/server/schedules/DeleteScheduleButton.tsx index 1710ad0a81..bdd4b4bfeb 100644 --- a/resources/scripts/components/server/schedules/DeleteScheduleButton.tsx +++ b/resources/scripts/components/server/schedules/DeleteScheduleButton.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import { useState } from 'react'; import deleteSchedule from '@/api/server/schedules/deleteSchedule'; import { ServerContext } from '@/state/server'; import { Actions, useStoreActions } from 'easy-peasy'; @@ -16,7 +16,7 @@ interface Props { export default ({ scheduleId, onDeleted }: Props) => { const [visible, setVisible] = useState(false); const [isLoading, setIsLoading] = useState(false); - const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid); + const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); const { addError, clearFlashes } = useStoreActions((actions: Actions) => actions.flashes); const onDelete = () => { @@ -27,7 +27,7 @@ export default ({ scheduleId, onDeleted }: Props) => { setIsLoading(false); onDeleted(); }) - .catch((error) => { + .catch(error => { console.error(error); addError({ key: 'schedules', message: httpErrorToHuman(error) }); diff --git a/resources/scripts/components/server/schedules/EditScheduleModal.tsx b/resources/scripts/components/server/schedules/EditScheduleModal.tsx index 6a89cb34c3..8ab8536565 100644 --- a/resources/scripts/components/server/schedules/EditScheduleModal.tsx +++ b/resources/scripts/components/server/schedules/EditScheduleModal.tsx @@ -1,4 +1,4 @@ -import React, { useContext, useEffect, useState } from 'react'; +import { useContext, useEffect, useState } from 'react'; import { Schedule } from '@/api/server/schedules/getServerSchedules'; import Field from '@/components/elements/Field'; import { Form, Formik, FormikHelpers } from 'formik'; @@ -34,8 +34,8 @@ const EditScheduleModal = ({ schedule }: Props) => { const { addError, clearFlashes } = useFlash(); const { dismiss } = useContext(ModalContext); - const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid); - const appendSchedule = ServerContext.useStoreActions((actions) => actions.schedules.appendSchedule); + const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); + const appendSchedule = ServerContext.useStoreActions(actions => actions.schedules.appendSchedule); const [showCheatsheet, setShowCheetsheet] = useState(false); useEffect(() => { @@ -59,12 +59,12 @@ const EditScheduleModal = ({ schedule }: Props) => { onlyWhenOnline: values.onlyWhenOnline, isActive: values.enabled, }) - .then((schedule) => { + .then(schedule => { setSubmitting(false); appendSchedule(schedule); dismiss(); }) - .catch((error) => { + .catch(error => { console.error(error); setSubmitting(false); @@ -114,7 +114,7 @@ const EditScheduleModal = ({ schedule }: Props) => { description={'Show the cron cheatsheet for some examples.'} label={'Show Cheatsheet'} defaultChecked={showCheatsheet} - onChange={() => setShowCheetsheet((s) => !s)} + onChange={() => setShowCheetsheet(s => !s)} /> {showCheatsheet && (
    diff --git a/resources/scripts/components/server/schedules/NewTaskButton.tsx b/resources/scripts/components/server/schedules/NewTaskButton.tsx index 7b480163fb..186b0a2ff5 100644 --- a/resources/scripts/components/server/schedules/NewTaskButton.tsx +++ b/resources/scripts/components/server/schedules/NewTaskButton.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import { useState } from 'react'; import { Schedule } from '@/api/server/schedules/getServerSchedules'; import TaskDetailsModal from '@/components/server/schedules/TaskDetailsModal'; import { Button } from '@/components/elements/button/index'; diff --git a/resources/scripts/components/server/schedules/RunScheduleButton.tsx b/resources/scripts/components/server/schedules/RunScheduleButton.tsx index 750b5440bf..7c1b4a69bc 100644 --- a/resources/scripts/components/server/schedules/RunScheduleButton.tsx +++ b/resources/scripts/components/server/schedules/RunScheduleButton.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useState } from 'react'; +import { useCallback, useState } from 'react'; import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; import { Button } from '@/components/elements/button/index'; import triggerScheduleExecution from '@/api/server/schedules/triggerScheduleExecution'; @@ -10,8 +10,8 @@ const RunScheduleButton = ({ schedule }: { schedule: Schedule }) => { const [loading, setLoading] = useState(false); const { clearFlashes, clearAndAddHttpError } = useFlash(); - const id = ServerContext.useStoreState((state) => state.server.data!.id); - const appendSchedule = ServerContext.useStoreActions((actions) => actions.schedules.appendSchedule); + const id = ServerContext.useStoreState(state => state.server.data!.id); + const appendSchedule = ServerContext.useStoreActions(actions => actions.schedules.appendSchedule); const onTriggerExecute = useCallback(() => { clearFlashes('schedule'); @@ -21,7 +21,7 @@ const RunScheduleButton = ({ schedule }: { schedule: Schedule }) => { setLoading(false); appendSchedule({ ...schedule, isProcessing: true }); }) - .catch((error) => { + .catch(error => { console.error(error); clearAndAddHttpError({ error, key: 'schedules' }); }) diff --git a/resources/scripts/components/server/schedules/ScheduleCheatsheetCards.tsx b/resources/scripts/components/server/schedules/ScheduleCheatsheetCards.tsx index 0eeed1527b..5a2edd4c22 100644 --- a/resources/scripts/components/server/schedules/ScheduleCheatsheetCards.tsx +++ b/resources/scripts/components/server/schedules/ScheduleCheatsheetCards.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import tw from 'twin.macro'; export default () => { diff --git a/resources/scripts/components/server/schedules/ScheduleContainer.tsx b/resources/scripts/components/server/schedules/ScheduleContainer.tsx index 1722314fb4..f644653e05 100644 --- a/resources/scripts/components/server/schedules/ScheduleContainer.tsx +++ b/resources/scripts/components/server/schedules/ScheduleContainer.tsx @@ -1,8 +1,7 @@ -import React, { useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; import getServerSchedules from '@/api/server/schedules/getServerSchedules'; import { ServerContext } from '@/state/server'; import Spinner from '@/components/elements/Spinner'; -import { useHistory, useRouteMatch } from 'react-router-dom'; import FlashMessageRender from '@/components/FlashMessageRender'; import ScheduleRow from '@/components/server/schedules/ScheduleRow'; import { httpErrorToHuman } from '@/api/http'; @@ -13,24 +12,23 @@ import tw from 'twin.macro'; import GreyRowBox from '@/components/elements/GreyRowBox'; import { Button } from '@/components/elements/button/index'; import ServerContentBlock from '@/components/elements/ServerContentBlock'; +import { Link } from 'react-router-dom'; -export default () => { - const match = useRouteMatch(); - const history = useHistory(); - - const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid); +function ScheduleContainer() { + const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); const { clearFlashes, addError } = useFlash(); const [loading, setLoading] = useState(true); const [visible, setVisible] = useState(false); - const schedules = ServerContext.useStoreState((state) => state.schedules.data); - const setSchedules = ServerContext.useStoreActions((actions) => actions.schedules.setSchedules); + const schedules = ServerContext.useStoreState(state => state.schedules.data); + const setSchedules = ServerContext.useStoreActions(actions => actions.schedules.setSchedules); useEffect(() => { clearFlashes('schedules'); + getServerSchedules(uuid) - .then((schedules) => setSchedules(schedules)) - .catch((error) => { + .then(schedules => setSchedules(schedules)) + .catch(error => { addError({ message: httpErrorToHuman(error), key: 'schedules' }); console.error(error); }) @@ -49,16 +47,13 @@ export default () => { There are no schedules configured for this server.

    ) : ( - schedules.map((schedule) => ( + schedules.map(schedule => ( + // @ts-expect-error go away { - e.preventDefault(); - history.push(`${match.url}/${schedule.id}`); - }} > @@ -76,4 +71,6 @@ export default () => { )} ); -}; +} + +export default ScheduleContainer; diff --git a/resources/scripts/components/server/schedules/ScheduleCronRow.tsx b/resources/scripts/components/server/schedules/ScheduleCronRow.tsx index 187478f551..282c05038a 100644 --- a/resources/scripts/components/server/schedules/ScheduleCronRow.tsx +++ b/resources/scripts/components/server/schedules/ScheduleCronRow.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import { Schedule } from '@/api/server/schedules/getServerSchedules'; import classNames from 'classnames'; diff --git a/resources/scripts/components/server/schedules/ScheduleEditContainer.tsx b/resources/scripts/components/server/schedules/ScheduleEditContainer.tsx index bacef24ef6..cca2cce988 100644 --- a/resources/scripts/components/server/schedules/ScheduleEditContainer.tsx +++ b/resources/scripts/components/server/schedules/ScheduleEditContainer.tsx @@ -1,5 +1,5 @@ -import React, { useCallback, useEffect, useState } from 'react'; -import { useHistory, useParams } from 'react-router-dom'; +import { useCallback, useEffect, useState } from 'react'; +import { useNavigate, useParams } from 'react-router-dom'; import getServerSchedule from '@/api/server/schedules/getServerSchedule'; import Spinner from '@/components/elements/Spinner'; import FlashMessageRender from '@/components/FlashMessageRender'; @@ -18,10 +18,6 @@ import { format } from 'date-fns'; import ScheduleCronRow from '@/components/server/schedules/ScheduleCronRow'; import RunScheduleButton from '@/components/server/schedules/RunScheduleButton'; -interface Params { - id: string; -} - const CronBox = ({ title, value }: { title: string; value: string }) => (

    {title}

    @@ -41,21 +37,21 @@ const ActivePill = ({ active }: { active: boolean }) => ( ); export default () => { - const history = useHistory(); - const { id: scheduleId } = useParams(); + const { id: scheduleId } = useParams<'id'>(); + const navigate = useNavigate(); - const id = ServerContext.useStoreState((state) => state.server.data!.id); - const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid); + const id = ServerContext.useStoreState(state => state.server.data!.id); + const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); const { clearFlashes, clearAndAddHttpError } = useFlash(); const [isLoading, setIsLoading] = useState(true); const [showEditModal, setShowEditModal] = useState(false); const schedule = ServerContext.useStoreState( - (st) => st.schedules.data.find((s) => s.id === Number(scheduleId)), - isEqual + st => st.schedules.data.find(s => s.id === Number(scheduleId)), + isEqual, ); - const appendSchedule = ServerContext.useStoreActions((actions) => actions.schedules.appendSchedule); + const appendSchedule = ServerContext.useStoreActions(actions => actions.schedules.appendSchedule); useEffect(() => { if (schedule?.id === Number(scheduleId)) { @@ -65,8 +61,8 @@ export default () => { clearFlashes('schedules'); getServerSchedule(uuid, Number(scheduleId)) - .then((schedule) => appendSchedule(schedule)) - .catch((error) => { + .then(schedule => appendSchedule(schedule)) + .catch(error => { console.error(error); clearAndAddHttpError({ error, key: 'schedules' }); }) @@ -74,7 +70,7 @@ export default () => { }, [scheduleId]); const toggleEditModal = useCallback(() => { - setShowEditModal((s) => !s); + setShowEditModal(s => !s); }, []); return ( @@ -140,9 +136,9 @@ export default () => { {schedule.tasks.length > 0 ? schedule.tasks .sort((a, b) => - a.sequenceId === b.sequenceId ? 0 : a.sequenceId > b.sequenceId ? 1 : -1 + a.sequenceId === b.sequenceId ? 0 : a.sequenceId > b.sequenceId ? 1 : -1, ) - .map((task) => ( + .map(task => ( { history.push(`/server/${id}/schedules`)} + onDeleted={() => navigate(`/server/${id}/schedules`)} /> {schedule.tasks.length > 0 && ( diff --git a/resources/scripts/components/server/schedules/ScheduleRow.tsx b/resources/scripts/components/server/schedules/ScheduleRow.tsx index 1a7da64c0e..030947cd7c 100644 --- a/resources/scripts/components/server/schedules/ScheduleRow.tsx +++ b/resources/scripts/components/server/schedules/ScheduleRow.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import { Schedule } from '@/api/server/schedules/getServerSchedules'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faCalendarAlt } from '@fortawesome/free-solid-svg-icons'; diff --git a/resources/scripts/components/server/schedules/ScheduleTaskRow.tsx b/resources/scripts/components/server/schedules/ScheduleTaskRow.tsx index f950b94f82..008c99f023 100644 --- a/resources/scripts/components/server/schedules/ScheduleTaskRow.tsx +++ b/resources/scripts/components/server/schedules/ScheduleTaskRow.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import { useState } from 'react'; import { Schedule, Task } from '@/api/server/schedules/getServerSchedules'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { @@ -40,12 +40,12 @@ const getActionDetails = (action: string): [string, any] => { }; export default ({ schedule, task }: Props) => { - const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid); + const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); const { clearFlashes, addError } = useFlash(); const [visible, setVisible] = useState(false); const [isLoading, setIsLoading] = useState(false); const [isEditing, setIsEditing] = useState(false); - const appendSchedule = ServerContext.useStoreActions((actions) => actions.schedules.appendSchedule); + const appendSchedule = ServerContext.useStoreActions(actions => actions.schedules.appendSchedule); const onConfirmDeletion = () => { setIsLoading(true); @@ -54,10 +54,10 @@ export default ({ schedule, task }: Props) => { .then(() => appendSchedule({ ...schedule, - tasks: schedule.tasks.filter((t) => t.id !== task.id), - }) + tasks: schedule.tasks.filter(t => t.id !== task.id), + }), ) - .catch((error) => { + .catch(error => { console.error(error); setIsLoading(false); addError({ message: httpErrorToHuman(error), key: 'schedules' }); diff --git a/resources/scripts/components/server/schedules/TaskDetailsModal.tsx b/resources/scripts/components/server/schedules/TaskDetailsModal.tsx index f54c0a2589..a52e59a33b 100644 --- a/resources/scripts/components/server/schedules/TaskDetailsModal.tsx +++ b/resources/scripts/components/server/schedules/TaskDetailsModal.tsx @@ -1,4 +1,4 @@ -import React, { useContext, useEffect } from 'react'; +import { useContext, useEffect } from 'react'; import { Schedule, Task } from '@/api/server/schedules/getServerSchedules'; import { Field as FormikField, Form, Formik, FormikHelpers, useField } from 'formik'; import { ServerContext } from '@/state/server'; @@ -35,7 +35,7 @@ interface Values { const schema = object().shape({ action: string().required().oneOf(['command', 'power', 'backup']), payload: string().when('action', { - is: (v) => v !== 'backup', + is: (v: string) => v !== 'backup', then: string().required('A task payload must be provided.'), otherwise: string(), }), @@ -68,9 +68,9 @@ 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); - const backupLimit = ServerContext.useStoreState((state) => state.server.data!.featureLimits.backups); + const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); + const appendSchedule = ServerContext.useStoreActions(actions => actions.schedules.appendSchedule); + const backupLimit = ServerContext.useStoreState(state => state.server.data!.featureLimits.backups); useEffect(() => { return () => { @@ -88,16 +88,16 @@ const TaskDetailsModal = ({ schedule, task }: Props) => { }); } else { createOrUpdateScheduleTask(uuid, schedule.id, task?.id, values) - .then((task) => { - let tasks = schedule.tasks.map((t) => (t.id === task.id ? task : t)); - if (!schedule.tasks.find((t) => t.id === task.id)) { + .then(task => { + let tasks = schedule.tasks.map(t => (t.id === task.id ? task : t)); + if (!schedule.tasks.find(t => t.id === task.id)) { tasks = [...tasks, task]; } appendSchedule({ ...schedule, tasks }); dismiss(); }) - .catch((error) => { + .catch(error => { console.error(error); setSubmitting(false); addError({ message: httpErrorToHuman(error), key: 'schedule:task' }); diff --git a/resources/scripts/components/server/settings/ReinstallServerBox.tsx b/resources/scripts/components/server/settings/ReinstallServerBox.tsx index 3a5a8dad51..0a5a157232 100644 --- a/resources/scripts/components/server/settings/ReinstallServerBox.tsx +++ b/resources/scripts/components/server/settings/ReinstallServerBox.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; import { ServerContext } from '@/state/server'; import TitledGreyBox from '@/components/elements/TitledGreyBox'; import reinstallServer from '@/api/server/reinstallServer'; @@ -10,7 +10,7 @@ import { Button } from '@/components/elements/button/index'; import { Dialog } from '@/components/elements/dialog'; export default () => { - const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid); + const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); const [modalVisible, setModalVisible] = useState(false); const { addFlash, clearFlashes } = useStoreActions((actions: Actions) => actions.flashes); @@ -24,7 +24,7 @@ export default () => { message: 'Your server has begun the reinstallation process.', }); }) - .catch((error) => { + .catch(error => { console.error(error); addFlash({ key: 'settings', type: 'error', message: httpErrorToHuman(error) }); diff --git a/resources/scripts/components/server/settings/RenameServerBox.tsx b/resources/scripts/components/server/settings/RenameServerBox.tsx index 9cb290a1ad..34d0508793 100644 --- a/resources/scripts/components/server/settings/RenameServerBox.tsx +++ b/resources/scripts/components/server/settings/RenameServerBox.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import { ServerContext } from '@/state/server'; import TitledGreyBox from '@/components/elements/TitledGreyBox'; import { Field as FormikField, Form, Formik, FormikHelpers, useFormikContext } from 'formik'; @@ -43,15 +42,15 @@ const RenameServerBox = () => { }; export default () => { - const server = ServerContext.useStoreState((state) => state.server.data!); - const setServer = ServerContext.useStoreActions((actions) => actions.server.setServer); + const server = ServerContext.useStoreState(state => state.server.data!); + const setServer = ServerContext.useStoreActions(actions => actions.server.setServer); const { addError, clearFlashes } = useStoreActions((actions: Actions) => actions.flashes); const submit = ({ name, description }: Values, { setSubmitting }: FormikHelpers) => { clearFlashes('settings'); renameServer(server.uuid, name, description) .then(() => setServer({ ...server, name, description })) - .catch((error) => { + .catch(error => { console.error(error); addError({ key: 'settings', message: httpErrorToHuman(error) }); }) @@ -63,7 +62,7 @@ export default () => { onSubmit={submit} initialValues={{ name: server.name, - description: server.description, + description: server.description ?? '', }} validationSchema={object().shape({ name: string().required().min(1), diff --git a/resources/scripts/components/server/settings/SettingsContainer.tsx b/resources/scripts/components/server/settings/SettingsContainer.tsx index bcdc80b8e0..e86aa07d99 100644 --- a/resources/scripts/components/server/settings/SettingsContainer.tsx +++ b/resources/scripts/components/server/settings/SettingsContainer.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import TitledGreyBox from '@/components/elements/TitledGreyBox'; import { ServerContext } from '@/state/server'; import { useStoreState } from 'easy-peasy'; @@ -16,11 +15,11 @@ import { ip } from '@/lib/formatters'; import { Button } from '@/components/elements/button/index'; export default () => { - const username = useStoreState((state) => state.user.data!.username); - const id = ServerContext.useStoreState((state) => state.server.data!.id); - const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid); - const node = ServerContext.useStoreState((state) => state.server.data!.node); - const sftp = ServerContext.useStoreState((state) => state.server.data!.sftpDetails, isEqual); + const username = useStoreState(state => state.user.data!.username); + const id = ServerContext.useStoreState(state => state.server.data!.id); + const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); + const node = ServerContext.useStoreState(state => state.server.data!.node); + const sftp = ServerContext.useStoreState(state => state.server.data!.sftpDetails, isEqual); return ( diff --git a/resources/scripts/components/server/startup/StartupContainer.tsx b/resources/scripts/components/server/startup/StartupContainer.tsx index 35bb9a63cc..ff8afa1294 100644 --- a/resources/scripts/components/server/startup/StartupContainer.tsx +++ b/resources/scripts/components/server/startup/StartupContainer.tsx @@ -1,4 +1,5 @@ -import React, { useCallback, useEffect, useState } from 'react'; +import { useCallback, useEffect, useState } from 'react'; +import * as React from 'react'; import TitledGreyBox from '@/components/elements/TitledGreyBox'; import tw from 'twin.macro'; import VariableBox from '@/components/server/startup/VariableBox'; @@ -20,14 +21,14 @@ const StartupContainer = () => { const [loading, setLoading] = useState(false); const { clearFlashes, clearAndAddHttpError } = useFlash(); - const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid); + const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); const variables = ServerContext.useStoreState( ({ server }) => ({ variables: server.data!.variables, invocation: server.data!.invocation, dockerImage: server.data!.dockerImage, }), - isEqual + isEqual, ); const { data, error, isValidating, mutate } = getServerStartup(uuid, { @@ -35,11 +36,11 @@ const StartupContainer = () => { dockerImages: { [variables.dockerImage]: variables.dockerImage }, }); - const setServerFromState = ServerContext.useStoreActions((actions) => actions.server.setServerFromState); + const setServerFromState = ServerContext.useStoreActions(actions => actions.server.setServerFromState); const isCustomImage = data && !Object.values(data.dockerImages) - .map((v) => v.toLowerCase()) + .map(v => v.toLowerCase()) .includes(variables.dockerImage.toLowerCase()); useEffect(() => { @@ -52,7 +53,7 @@ const StartupContainer = () => { useDeepCompareEffect(() => { if (!data) return; - setServerFromState((s) => ({ + setServerFromState(s => ({ ...s, invocation: data.invocation, variables: data.variables, @@ -66,14 +67,14 @@ const StartupContainer = () => { const image = v.currentTarget.value; setSelectedDockerImage(uuid, image) - .then(() => setServerFromState((s) => ({ ...s, dockerImage: image }))) - .catch((error) => { + .then(() => setServerFromState(s => ({ ...s, dockerImage: image }))) + .catch(error => { console.error(error); clearAndAddHttpError({ key: 'startup:image', error }); }) .then(() => setLoading(false)); }, - [uuid] + [uuid], ); return !data ? ( @@ -99,7 +100,7 @@ const StartupContainer = () => { onChange={updateSelectedDockerImage} defaultValue={variables.dockerImage} > - {Object.keys(data.dockerImages).map((key) => ( + {Object.keys(data.dockerImages).map(key => ( @@ -126,7 +127,7 @@ const StartupContainer = () => {

    Variables

    - {data.variables.map((variable) => ( + {data.variables.map(variable => ( ))}
    diff --git a/resources/scripts/components/server/startup/VariableBox.tsx b/resources/scripts/components/server/startup/VariableBox.tsx index ad73615c64..3e38b435b5 100644 --- a/resources/scripts/components/server/startup/VariableBox.tsx +++ b/resources/scripts/components/server/startup/VariableBox.tsx @@ -1,4 +1,4 @@ -import React, { memo, useState } from 'react'; +import { memo, useState } from 'react'; import { ServerEggVariable } from '@/api/server/types'; import TitledGreyBox from '@/components/elements/TitledGreyBox'; import { usePermissions } from '@/plugins/usePermissions'; @@ -22,7 +22,7 @@ interface Props { const VariableBox = ({ variable }: Props) => { const FLASH_KEY = `server:startup:${variable.envVariable}`; - const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid); + const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); const [loading, setLoading] = useState(false); const [canEdit] = usePermissions(['startup.update']); const { clearFlashes, clearAndAddHttpError } = useFlash(); @@ -35,28 +35,28 @@ const VariableBox = ({ variable }: Props) => { updateStartupVariable(uuid, variable.envVariable, value) .then(([response, invocation]) => mutate( - (data) => ({ - ...data, + data => ({ + ...data!, invocation, - variables: (data.variables || []).map((v) => - v.envVariable === response.envVariable ? response : v + variables: (data!.variables || []).map(v => + v.envVariable === response.envVariable ? response : v, ), }), - false - ) + false, + ), ) - .catch((error) => { + .catch(error => { console.error(error); - clearAndAddHttpError({ error, key: FLASH_KEY }); + clearAndAddHttpError({ key: FLASH_KEY, error }); }) .then(() => setLoading(false)); }, 500); const useSwitch = variable.rules.some( - (v) => v === 'boolean' || v === 'in:0,1' || v === 'in:1,0' || v === 'in:true,false' || v === 'in:false,true' + v => v === 'boolean' || v === 'in:0,1' || v === 'in:1,0' || v === 'in:true,false' || v === 'in:false,true', ); - const isStringSwitch = variable.rules.some((v) => v === 'string'); - const selectValues = variable.rules.find((v) => v.startsWith('in:'))?.split(',') || []; + const isStringSwitch = variable.rules.some(v => v === 'string'); + const selectValues = variable.rules.find(v => v.startsWith('in:'))?.split(',') || []; return ( { {selectValues.length > 0 ? ( <> { + onKeyUp={e => { if (canEdit && variable.isEditable) { setVariableValue(e.currentTarget.value); } diff --git a/resources/scripts/components/server/users/AddSubuserButton.tsx b/resources/scripts/components/server/users/AddSubuserButton.tsx index d4847ce815..42bfa6a4f7 100644 --- a/resources/scripts/components/server/users/AddSubuserButton.tsx +++ b/resources/scripts/components/server/users/AddSubuserButton.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import { useState } from 'react'; import EditSubuserModal from '@/components/server/users/EditSubuserModal'; import { Button } from '@/components/elements/button/index'; diff --git a/resources/scripts/components/server/users/EditSubuserModal.tsx b/resources/scripts/components/server/users/EditSubuserModal.tsx index c28d56f6c2..86adca9cf6 100644 --- a/resources/scripts/components/server/users/EditSubuserModal.tsx +++ b/resources/scripts/components/server/users/EditSubuserModal.tsx @@ -1,4 +1,4 @@ -import React, { useContext, useEffect, useRef } from 'react'; +import { useContext, useEffect, useRef } from 'react'; import { Subuser } from '@/state/server/subusers'; import { Form, Formik } from 'formik'; import { array, object, string } from 'yup'; @@ -29,24 +29,24 @@ interface Values { const EditSubuserModal = ({ subuser }: Props) => { const ref = useRef(null); - const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid); - const appendSubuser = ServerContext.useStoreActions((actions) => actions.subusers.appendSubuser); + const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); + const appendSubuser = ServerContext.useStoreActions(actions => actions.subusers.appendSubuser); const { clearFlashes, clearAndAddHttpError } = useStoreActions( - (actions: Actions) => actions.flashes + (actions: Actions) => actions.flashes, ); const { dismiss, setPropOverrides } = useContext(ModalContext); - const isRootAdmin = useStoreState((state) => state.user.data!.rootAdmin); - const permissions = useStoreState((state) => state.permissions.data); + const isRootAdmin = useStoreState(state => state.user.data!.rootAdmin); + const permissions = useStoreState(state => state.permissions.data); // The currently logged in user's permissions. We're going to filter out any permissions // that they should not need. - const loggedInPermissions = ServerContext.useStoreState((state) => state.server.permissions); + const loggedInPermissions = ServerContext.useStoreState(state => state.server.permissions); const [canEditUser] = usePermissions(subuser ? ['user.update'] : ['user.create']); // The permissions that can be modified by this user. const editablePermissions = useDeepCompareMemo(() => { - const cleaned = Object.keys(permissions).map((key) => - Object.keys(permissions[key].keys).map((pkey) => `${key}.${pkey}`) + const cleaned = Object.keys(permissions).map(key => + Object.keys(permissions[key]?.keys ?? {}).map(pkey => `${key}.${pkey}`), ); const list: string[] = ([] as string[]).concat.apply([], Object.values(cleaned)); @@ -55,7 +55,7 @@ const EditSubuserModal = ({ subuser }: Props) => { return list; } - return list.filter((key) => loggedInPermissions.indexOf(key) >= 0); + return list.filter(key => loggedInPermissions.indexOf(key) >= 0); }, [isRootAdmin, permissions, loggedInPermissions]); const submit = (values: Values) => { @@ -63,11 +63,11 @@ const EditSubuserModal = ({ subuser }: Props) => { clearFlashes('user:edit'); createOrUpdateSubuser(uuid, values, subuser) - .then((subuser) => { + .then(subuser => { appendSubuser(subuser); dismiss(); }) - .catch((error) => { + .catch(error => { console.error(error); setPropOverrides(null); clearAndAddHttpError({ key: 'user:edit', error }); @@ -82,7 +82,7 @@ const EditSubuserModal = ({ subuser }: Props) => { () => () => { clearFlashes('user:edit'); }, - [] + [], ); return ( @@ -137,17 +137,17 @@ const EditSubuserModal = ({ subuser }: Props) => { )}
    {Object.keys(permissions) - .filter((key) => key !== 'websocket') + .filter(key => key !== 'websocket') .map((key, index) => ( `${key}.${pkey}`)} + permissions={Object.keys(permissions[key]?.keys ?? {}).map(pkey => `${key}.${pkey}`)} css={index > 0 ? tw`mt-4` : undefined} > -

    {permissions[key].description}

    - {Object.keys(permissions[key].keys).map((pkey) => ( +

    {permissions[key]?.description}

    + {Object.keys(permissions[key]?.keys ?? {}).map(pkey => ( { - const [key, pkey] = permission.split('.', 2); - const permissions = useStoreState((state) => state.permissions.data); + const [key = '', pkey = ''] = permission.split('.', 2); + const permissions = useStoreState(state => state.permissions.data); return ( @@ -54,8 +53,8 @@ const PermissionRow = ({ permission, disabled }: Props) => { - {permissions[key].keys[pkey].length > 0 && ( -

    {permissions[key].keys[pkey]}

    + {(permissions[key]?.keys?.[pkey]?.length ?? 0) > 0 && ( +

    {permissions[key]?.keys?.[pkey] ?? ''}

    )}
    diff --git a/resources/scripts/components/server/users/PermissionTitleBox.tsx b/resources/scripts/components/server/users/PermissionTitleBox.tsx index 1d678f2215..b8f8fd7b3a 100644 --- a/resources/scripts/components/server/users/PermissionTitleBox.tsx +++ b/resources/scripts/components/server/users/PermissionTitleBox.tsx @@ -1,29 +1,33 @@ -import React, { memo, useCallback } from 'react'; import { useField } from 'formik'; -import TitledGreyBox from '@/components/elements/TitledGreyBox'; +import type { ReactNode } from 'react'; +import { memo, useCallback } from 'react'; +import isEqual from 'react-fast-compare'; import tw from 'twin.macro'; + +import TitledGreyBox from '@/components/elements/TitledGreyBox'; import Input from '@/components/elements/Input'; -import isEqual from 'react-fast-compare'; interface Props { - isEditable: boolean; + children?: ReactNode; + className?: string; + + isEditable?: boolean; title: string; permissions: string[]; - className?: string; } -const PermissionTitleBox: React.FC = memo(({ isEditable, title, permissions, className, children }) => { +function PermissionTitleBox({ isEditable, title, permissions, className, children }: Props) { const [{ value }, , { setValue }] = useField('permissions'); const onCheckboxClicked = useCallback( (e: React.ChangeEvent) => { if (e.currentTarget.checked) { - setValue([...value, ...permissions.filter((p) => !value.includes(p))]); + setValue([...value, ...permissions.filter(p => !value.includes(p))]); } else { - setValue(value.filter((p) => !permissions.includes(p))); + setValue(value.filter(p => !permissions.includes(p))); } }, - [permissions, value] + [permissions, value], ); return ( @@ -34,7 +38,7 @@ const PermissionTitleBox: React.FC = memo(({ isEditable, title, permissio {isEditable && ( value.includes(p))} + checked={permissions.every(p => value.includes(p))} onChange={onCheckboxClicked} /> )} @@ -45,6 +49,8 @@ const PermissionTitleBox: React.FC = memo(({ isEditable, title, permissio {children}
    ); -}, isEqual); +} + +const MemoizedPermissionTitleBox = memo(PermissionTitleBox, isEqual); -export default PermissionTitleBox; +export default MemoizedPermissionTitleBox; diff --git a/resources/scripts/components/server/users/RemoveSubuserButton.tsx b/resources/scripts/components/server/users/RemoveSubuserButton.tsx index defc6b47f3..eb4294d9bb 100644 --- a/resources/scripts/components/server/users/RemoveSubuserButton.tsx +++ b/resources/scripts/components/server/users/RemoveSubuserButton.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import { useState } from 'react'; import ConfirmationModal from '@/components/elements/ConfirmationModal'; import { ServerContext } from '@/state/server'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; @@ -14,8 +14,8 @@ export default ({ subuser }: { subuser: Subuser }) => { const [loading, setLoading] = useState(false); const [showConfirmation, setShowConfirmation] = useState(false); - const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid); - const removeSubuser = ServerContext.useStoreActions((actions) => actions.subusers.removeSubuser); + const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); + const removeSubuser = ServerContext.useStoreActions(actions => actions.subusers.removeSubuser); const { addError, clearFlashes } = useStoreActions((actions: Actions) => actions.flashes); const doDeletion = () => { @@ -26,7 +26,7 @@ export default ({ subuser }: { subuser: Subuser }) => { setLoading(false); removeSubuser(subuser.uuid); }) - .catch((error) => { + .catch(error => { console.error(error); addError({ key: 'users', message: httpErrorToHuman(error) }); setShowConfirmation(false); diff --git a/resources/scripts/components/server/users/UserRow.tsx b/resources/scripts/components/server/users/UserRow.tsx index 45693c5d49..0265daa2f0 100644 --- a/resources/scripts/components/server/users/UserRow.tsx +++ b/resources/scripts/components/server/users/UserRow.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import { useState } from 'react'; import { Subuser } from '@/state/server/subusers'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faPencilAlt, faUnlockAlt, faUserLock } from '@fortawesome/free-solid-svg-icons'; @@ -14,7 +14,7 @@ interface Props { } export default ({ subuser }: Props) => { - const uuid = useStoreState((state) => state.user!.data!.uuid); + const uuid = useStoreState(state => state.user!.data!.uuid); const [visible, setVisible] = useState(false); return ( @@ -40,7 +40,7 @@ export default ({ subuser }: Props) => {
    diff --git a/resources/scripts/components/server/users/UsersContainer.tsx b/resources/scripts/components/server/users/UsersContainer.tsx index a5d505fe18..01128e6685 100644 --- a/resources/scripts/components/server/users/UsersContainer.tsx +++ b/resources/scripts/components/server/users/UsersContainer.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; import { ServerContext } from '@/state/server'; import { Actions, useStoreActions, useStoreState } from 'easy-peasy'; import { ApplicationStore } from '@/state'; @@ -15,9 +15,9 @@ import tw from 'twin.macro'; export default () => { const [loading, setLoading] = useState(true); - const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid); - const subusers = ServerContext.useStoreState((state) => state.subusers.data); - const setSubusers = ServerContext.useStoreActions((actions) => actions.subusers.setSubusers); + const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); + const subusers = ServerContext.useStoreState(state => state.subusers.data); + const setSubusers = ServerContext.useStoreActions(actions => actions.subusers.setSubusers); const permissions = useStoreState((state: ApplicationStore) => state.permissions.data); const getPermissions = useStoreActions((actions: Actions) => actions.permissions.getPermissions); @@ -26,18 +26,18 @@ export default () => { useEffect(() => { clearFlashes('users'); getServerSubusers(uuid) - .then((subusers) => { + .then(subusers => { setSubusers(subusers); setLoading(false); }) - .catch((error) => { + .catch(error => { console.error(error); addError({ key: 'users', message: httpErrorToHuman(error) }); }); }, []); useEffect(() => { - getPermissions().catch((error) => { + getPermissions().catch(error => { addError({ key: 'users', message: httpErrorToHuman(error) }); console.error(error); }); @@ -53,7 +53,7 @@ export default () => { {!subusers.length ? (

    It looks like you don't have any subusers.

    ) : ( - subusers.map((subuser) => ) + subusers.map(subuser => ) )}
    diff --git a/resources/scripts/context/ModalContext.ts b/resources/scripts/context/ModalContext.ts index cfda13de14..270cdb610c 100644 --- a/resources/scripts/context/ModalContext.ts +++ b/resources/scripts/context/ModalContext.ts @@ -1,4 +1,4 @@ -import React from 'react'; +import { createContext } from 'react'; import { SettableModalProps } from '@/hoc/asModal'; export interface ModalContextValues { @@ -7,11 +7,11 @@ export interface ModalContextValues { value: | ((current: Readonly>) => Partial) | Partial - | null + | null, ) => void; } -const ModalContext = React.createContext({ +const ModalContext = createContext({ dismiss: () => null, setPropOverrides: () => null, }); diff --git a/resources/scripts/helpers.ts b/resources/scripts/helpers.ts index 210ce040eb..a09a15fe8c 100644 --- a/resources/scripts/helpers.ts +++ b/resources/scripts/helpers.ts @@ -40,7 +40,7 @@ export function fileBitsToString(mode: string, directory: boolean): string { export function encodePathSegments(path: string): string { return path .split('/') - .map((s) => encodeURIComponent(s)) + .map(s => encodeURIComponent(s)) .join('/'); } diff --git a/resources/scripts/hoc/RequireServerPermission.tsx b/resources/scripts/hoc/RequireServerPermission.tsx index cb7da2b315..9b112f430d 100644 --- a/resources/scripts/hoc/RequireServerPermission.tsx +++ b/resources/scripts/hoc/RequireServerPermission.tsx @@ -1,12 +1,15 @@ -import React from 'react'; +import type { ReactNode } from 'react'; + import Can from '@/components/elements/Can'; import { ServerError } from '@/components/elements/ScreenBlock'; export interface RequireServerPermissionProps { + children?: ReactNode; + permissions: string | string[]; } -const RequireServerPermission: React.FC = ({ children, permissions }) => { +function RequireServerPermission({ children, permissions }: RequireServerPermissionProps) { return ( = ({ child {children} ); -}; +} export default RequireServerPermission; diff --git a/resources/scripts/hoc/asDialog.tsx b/resources/scripts/hoc/asDialog.tsx index 0be5dbe659..3ea5d4dc60 100644 --- a/resources/scripts/hoc/asDialog.tsx +++ b/resources/scripts/hoc/asDialog.tsx @@ -1,10 +1,11 @@ -import React, { useState } from 'react'; +import type { ComponentProps, ComponentType, FunctionComponent } from 'react'; +import { useState } from 'react'; import { Dialog, DialogProps, DialogWrapperContext, WrapperProps } from '@/components/elements/dialog'; function asDialog( - initialProps?: WrapperProps + initialProps?: WrapperProps, // eslint-disable-next-line @typescript-eslint/ban-types -):

    (C: React.ComponentType

    ) => React.FunctionComponent

    { +):

    (C: ComponentType

    ) => FunctionComponent

    { return function (Component) { return function ({ open, onClose, ...rest }) { const [props, setProps] = useState(initialProps || {}); @@ -12,7 +13,7 @@ function asDialog( return (

    - )} /> + )} /> ); diff --git a/resources/scripts/hoc/asModal.tsx b/resources/scripts/hoc/asModal.tsx index 142ead1d58..ae0f166c7a 100644 --- a/resources/scripts/hoc/asModal.tsx +++ b/resources/scripts/hoc/asModal.tsx @@ -1,7 +1,8 @@ -import React from 'react'; +import { PureComponent } from 'react'; +import isEqual from 'react-fast-compare'; + import PortaledModal, { ModalProps } from '@/components/elements/Modal'; import ModalContext, { ModalContextValues } from '@/context/ModalContext'; -import isEqual from 'react-fast-compare'; export interface AsModalProps { visible: boolean; @@ -16,14 +17,12 @@ interface State { propOverrides: Partial; } -type ExtendedComponentType = (C: React.ComponentType) => React.ComponentType; - // eslint-disable-next-line @typescript-eslint/ban-types function asModal

    ( - modalProps?: SettableModalProps | ((props: P) => SettableModalProps) -): ExtendedComponentType

    { + modalProps?: SettableModalProps | ((props: P) => SettableModalProps), +): (Component: any) => any { return function (Component) { - return class extends React.PureComponent

    { + return class extends PureComponent

    { static displayName = `asModal(${Component.displayName})`; constructor(props: P & AsModalProps) { @@ -47,7 +46,7 @@ function asModal

    ( /** * @this {React.PureComponent

    } */ - componentDidUpdate(prevProps: Readonly

    , prevState: Readonly) { + override componentDidUpdate(prevProps: Readonly

    , prevState: Readonly) { if (prevProps.visible && !this.props.visible) { this.setState({ visible: false, propOverrides: {} }); } else if (!prevProps.visible && this.props.visible) { @@ -60,15 +59,15 @@ function asModal

    ( dismiss = () => this.setState({ visible: false }); - setPropOverrides: ModalContextValues['setPropOverrides'] = (value) => - this.setState((state) => ({ + setPropOverrides: ModalContextValues['setPropOverrides'] = value => + this.setState(state => ({ propOverrides: !value ? {} : typeof value === 'function' ? value(state.propOverrides) : value, })); /** * @this {React.PureComponent

    } */ - render() { + override render() { if (!this.state.render) return null; return ( diff --git a/resources/scripts/i18n.ts b/resources/scripts/i18n.ts index c36879eab3..2cb6565583 100644 --- a/resources/scripts/i18n.ts +++ b/resources/scripts/i18n.ts @@ -6,7 +6,7 @@ import I18NextMultiloadBackendAdapter from 'i18next-multiload-backend-adapter'; // If we're using HMR use a unique hash per page reload so that we're always // doing cache busting. Otherwise just use the builder provided hash value in // the URL to allow cache busting to occur whenever the front-end is rebuilt. -const hash = module.hot ? Date.now().toString(16) : process.env.WEBPACK_BUILD_HASH; +const hash = Date.now().toString(16); i18n.use(I18NextMultiloadBackendAdapter) .use(initReactI18next) diff --git a/resources/scripts/index.tsx b/resources/scripts/index.tsx index 2aade24755..99fd50d375 100644 --- a/resources/scripts/index.tsx +++ b/resources/scripts/index.tsx @@ -1,16 +1,7 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import App from '@/components/App'; -import { setConfig } from 'react-hot-loader'; +import { createRoot } from 'react-dom/client'; +import { App } from '@/components/App'; // Enable language support. import './i18n'; -// Prevents page reloads while making component changes which -// also avoids triggering constant loading indicators all over -// the place in development. -// -// @see https://github.com/gaearon/react-hot-loader#hook-support -setConfig({ reloadHooks: false }); - -ReactDOM.render(, document.getElementById('app')); +createRoot(document.getElementById('app')!).render(); diff --git a/resources/scripts/lib/formatters.spec.ts b/resources/scripts/lib/formatters.spec.ts index c03008971b..c786f5869d 100644 --- a/resources/scripts/lib/formatters.spec.ts +++ b/resources/scripts/lib/formatters.spec.ts @@ -1,17 +1,19 @@ +import { describe, expect, it } from 'vitest'; + import { bytesToString, ip, mbToBytes } from '@/lib/formatters'; -describe('@/lib/formatters.ts', function () { - describe('mbToBytes()', function () { - it('should convert from MB to Bytes', function () { - expect(mbToBytes(1)).toBe(1_048_576); - expect(mbToBytes(0)).toBe(0); - expect(mbToBytes(0.1)).toBe(104_857); - expect(mbToBytes(0.001)).toBe(1_048); - expect(mbToBytes(1024)).toBe(1_073_741_824); +describe('@/lib/formatters.ts', () => { + describe('mbToBytes()', () => { + it('should convert from MB to Bytes', () => { + expect(mbToBytes(1)).equals(1_048_576); + expect(mbToBytes(0)).equals(0); + expect(mbToBytes(0.1)).equals(104_857); + expect(mbToBytes(0.001)).equals(1_048); + expect(mbToBytes(1024)).equals(1_073_741_824); }); }); - describe('bytesToString()', function () { + describe('bytesToString()', () => { it.each([ [0, '0 Bytes'], [0.5, '0 Bytes'], @@ -38,24 +40,24 @@ describe('@/lib/formatters.ts', function () { [1_000_000_000_000, '931.32 GiB'], [1_099_511_627_776, '1 TiB'], ])('should format %d bytes as "%s"', function (input, output) { - expect(bytesToString(input)).toBe(output); + expect(bytesToString(input)).equals(output); }); }); - describe('ip()', function () { - it('should format an IPv4 address', function () { - expect(ip('127.0.0.1')).toBe('127.0.0.1'); + describe('ip()', () => { + it('should format an IPv4 address', () => { + expect(ip('127.0.0.1')).equals('127.0.0.1'); }); - it('should format an IPv6 address', function () { - expect(ip(':::1')).toBe('[:::1]'); - expect(ip('2001:db8::')).toBe('[2001:db8::]'); + it('should format an IPv6 address', () => { + expect(ip(':::1')).equals('[:::1]'); + expect(ip('2001:db8::')).equals('[2001:db8::]'); }); - it('should handle random inputs', function () { - expect(ip('1')).toBe('1'); - expect(ip('foobar')).toBe('foobar'); - expect(ip('127.0.0.1:25565')).toBe('[127.0.0.1:25565]'); + it('should handle random inputs', () => { + expect(ip('1')).equals('1'); + expect(ip('foobar')).equals('foobar'); + expect(ip('127.0.0.1:25565')).equals('[127.0.0.1:25565]'); }); }); }); diff --git a/resources/scripts/lib/helpers.spec.ts b/resources/scripts/lib/helpers.spec.ts index 6d18e7e3df..b22f60f987 100644 --- a/resources/scripts/lib/helpers.spec.ts +++ b/resources/scripts/lib/helpers.spec.ts @@ -1,29 +1,31 @@ +import { describe, expect, it } from 'vitest'; + import { hexToRgba } from '@/lib/helpers'; -describe('@/lib/helpers.ts', function () { - describe('hexToRgba()', function () { - it('should return the expected rgba', function () { - expect(hexToRgba('#ffffff')).toBe('rgba(255, 255, 255, 1)'); - expect(hexToRgba('#00aabb')).toBe('rgba(0, 170, 187, 1)'); - expect(hexToRgba('#efefef')).toBe('rgba(239, 239, 239, 1)'); +describe('@/lib/helpers.ts', () => { + describe('hexToRgba()', () => { + it('should return the expected rgba', () => { + expect(hexToRgba('#ffffff')).equals('rgba(255, 255, 255, 1)'); + expect(hexToRgba('#00aabb')).equals('rgba(0, 170, 187, 1)'); + expect(hexToRgba('#efefef')).equals('rgba(239, 239, 239, 1)'); }); - it('should ignore case', function () { - expect(hexToRgba('#FF00A3')).toBe('rgba(255, 0, 163, 1)'); + it('should ignore case', () => { + expect(hexToRgba('#FF00A3')).equals('rgba(255, 0, 163, 1)'); }); - it('should allow alpha channel changes', function () { - expect(hexToRgba('#ece5a8', 0.5)).toBe('rgba(236, 229, 168, 0.5)'); - expect(hexToRgba('#ece5a8', 0.1)).toBe('rgba(236, 229, 168, 0.1)'); - expect(hexToRgba('#000000', 0)).toBe('rgba(0, 0, 0, 0)'); + it('should allow alpha channel changes', () => { + expect(hexToRgba('#ece5a8', 0.5)).equals('rgba(236, 229, 168, 0.5)'); + expect(hexToRgba('#ece5a8', 0.1)).equals('rgba(236, 229, 168, 0.1)'); + expect(hexToRgba('#000000', 0)).equals('rgba(0, 0, 0, 0)'); }); - it('should handle invalid strings', function () { - expect(hexToRgba('')).toBe(''); - expect(hexToRgba('foobar')).toBe('foobar'); - expect(hexToRgba('#fff')).toBe('#fff'); - expect(hexToRgba('#')).toBe('#'); - expect(hexToRgba('#fffffy')).toBe('#fffffy'); + it('should handle invalid strings', () => { + expect(hexToRgba('')).equals(''); + expect(hexToRgba('foobar')).equals('foobar'); + expect(hexToRgba('#fff')).equals('#fff'); + expect(hexToRgba('#')).equals('#'); + expect(hexToRgba('#fffffy')).equals('#fffffy'); }); }); }); diff --git a/resources/scripts/lib/helpers.ts b/resources/scripts/lib/helpers.ts index ee293a2bba..4d5fb581e3 100644 --- a/resources/scripts/lib/helpers.ts +++ b/resources/scripts/lib/helpers.ts @@ -9,7 +9,7 @@ function hexToRgba(hex: string, alpha = 1): string { } // noinspection RegExpSimplifiable - const [r, g, b] = hex.match(/[a-fA-F0-9]{2}/g)!.map((v) => parseInt(v, 16)); + const [r, g, b] = hex.match(/[a-fA-F0-9]{2}/g)!.map(v => parseInt(v, 16)); return `rgba(${r}, ${g}, ${b}, ${alpha})`; } diff --git a/resources/scripts/lib/objects.spec.ts b/resources/scripts/lib/objects.spec.ts index 330b6e954f..03a6fbdd1c 100644 --- a/resources/scripts/lib/objects.spec.ts +++ b/resources/scripts/lib/objects.spec.ts @@ -1,22 +1,24 @@ +import { describe, expect, it } from 'vitest'; + import { isObject } from '@/lib/objects'; -describe('@/lib/objects.ts', function () { - describe('isObject()', function () { - it('should return true for objects', function () { - expect(isObject({})).toBe(true); - expect(isObject({ foo: 123 })).toBe(true); - expect(isObject(Object.freeze({}))).toBe(true); +describe('@/lib/objects.ts', () => { + describe('isObject()', () => { + it('should return true for objects', () => { + expect(isObject({})).equals(true); + expect(isObject({ foo: 123 })).equals(true); + expect(isObject(Object.freeze({}))).equals(true); }); - it('should return false for null', function () { - expect(isObject(null)).toBe(false); + it('should return false for null', () => { + expect(isObject(null)).equals(false); }); it.each([undefined, 123, 'foobar', () => ({}), Function, String(123), isObject, () => null, [], [1, 2, 3]])( 'should return false for %p', - function (value) { - expect(isObject(value)).toBe(false); - } + value => { + expect(isObject(value)).equals(false); + }, ); }); }); diff --git a/resources/scripts/lib/strings.spec.ts b/resources/scripts/lib/strings.spec.ts index 8875ce2174..2c39ca2eff 100644 --- a/resources/scripts/lib/strings.spec.ts +++ b/resources/scripts/lib/strings.spec.ts @@ -1,14 +1,16 @@ +import { describe, expect, it } from 'vitest'; + import { capitalize } from '@/lib/strings'; -describe('@/lib/strings.ts', function () { - describe('capitalize()', function () { - it('should capitalize a string', function () { - expect(capitalize('foo bar')).toBe('Foo bar'); - expect(capitalize('FOOBAR')).toBe('Foobar'); +describe('@/lib/strings.ts', () => { + describe('capitalize()', () => { + it('should capitalize a string', () => { + expect(capitalize('foo bar')).equals('Foo bar'); + expect(capitalize('FOOBAR')).equals('Foobar'); }); - it('should handle empty strings', function () { - expect(capitalize('')).toBe(''); + it('should handle empty strings', () => { + expect(capitalize('')).equals(''); }); }); }); diff --git a/resources/scripts/macros.d.ts b/resources/scripts/macros.d.ts index a710784829..410e3bcb96 100644 --- a/resources/scripts/macros.d.ts +++ b/resources/scripts/macros.d.ts @@ -15,10 +15,10 @@ declare module 'styled-components' { T extends object, // eslint-disable-next-line @typescript-eslint/ban-types O extends object = {}, - A extends keyof any = never + A extends keyof any = never, > extends ForwardRefExoticBase> { ( - props: StyledComponentProps & { as?: Element | string; forwardedAs?: never | undefined } + props: StyledComponentProps & { as?: Element | string; forwardedAs?: never | undefined }, ): ReactElement>; } } diff --git a/resources/scripts/plugins/Websocket.ts b/resources/scripts/plugins/Websocket.ts index 48d8dc7e7a..8308afa5d6 100644 --- a/resources/scripts/plugins/Websocket.ts +++ b/resources/scripts/plugins/Websocket.ts @@ -26,7 +26,7 @@ export class Websocket extends EventEmitter { this.url = url; this.socket = new Sockette(`${this.url}`, { - onmessage: (e) => { + onmessage: e => { try { const { event, args } = JSON.parse(e.data); args ? this.emit(event, ...args) : this.emit(event); @@ -47,7 +47,7 @@ export class Websocket extends EventEmitter { this.authenticate(); }, onclose: () => this.emit('SOCKET_CLOSE'), - onerror: (error) => this.emit('SOCKET_ERROR', error), + onerror: error => this.emit('SOCKET_ERROR', error), }); this.timer = setTimeout(() => { @@ -100,7 +100,7 @@ export class Websocket extends EventEmitter { JSON.stringify({ event, args: Array.isArray(payload) ? payload : [payload], - }) + }), ); } } diff --git a/resources/scripts/plugins/useEventListener.ts b/resources/scripts/plugins/useEventListener.ts index 3fef05c4c1..b552bee198 100644 --- a/resources/scripts/plugins/useEventListener.ts +++ b/resources/scripts/plugins/useEventListener.ts @@ -3,7 +3,7 @@ import { useEffect, useRef } from 'react'; export default ( eventName: string, handler: (e: Event | CustomEvent | UIEvent | any) => void, - options?: boolean | EventListenerOptions + options?: boolean | EventListenerOptions, ) => { const savedHandler = useRef(null); diff --git a/resources/scripts/plugins/useFileManagerSwr.ts b/resources/scripts/plugins/useFileManagerSwr.ts index a03ccaefb3..e134cb4021 100644 --- a/resources/scripts/plugins/useFileManagerSwr.ts +++ b/resources/scripts/plugins/useFileManagerSwr.ts @@ -6,8 +6,8 @@ import { ServerContext } from '@/state/server'; export const getDirectorySwrKey = (uuid: string, directory: string): string => `${uuid}:files:${directory}`; export default () => { - const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid); - const directory = ServerContext.useStoreState((state) => state.files.directory); + const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); + const directory = ServerContext.useStoreState(state => state.files.directory); return useSWR( getDirectorySwrKey(uuid, directory), @@ -17,6 +17,6 @@ export default () => { revalidateOnMount: false, refreshInterval: 0, errorRetryCount: 2, - } + }, ); }; diff --git a/resources/scripts/plugins/useFlash.ts b/resources/scripts/plugins/useFlash.ts index 947975528e..2d570592cb 100644 --- a/resources/scripts/plugins/useFlash.ts +++ b/resources/scripts/plugins/useFlash.ts @@ -18,7 +18,7 @@ const useFlashKey = (key: string): KeyedFlashStore => { return { addError: (message, title) => addFlash({ key, message, title, type: 'error' }), clearFlashes: () => clearFlashes(key), - clearAndAddHttpError: (error) => clearAndAddHttpError({ key, error }), + clearAndAddHttpError: error => clearAndAddHttpError({ key, error }), }; }; diff --git a/resources/scripts/plugins/useLocationHash.ts b/resources/scripts/plugins/useLocationHash.ts index 6137e897cb..e6e377139a 100644 --- a/resources/scripts/plugins/useLocationHash.ts +++ b/resources/scripts/plugins/useLocationHash.ts @@ -9,7 +9,7 @@ export default () => { .substring(1) .split('&') .reduce((obj, str) => { - const [key, value = ''] = str.split('='); + const [key = '', value = ''] = str.split('='); return !str.trim() ? obj : { ...obj, [key]: value }; }, {}); @@ -18,11 +18,11 @@ export default () => { const current = getHashObject(location.hash); for (const key in params) { - current[key] = params[key]; + current[key] = params[key] ?? ''; } return Object.keys(current) - .map((key) => `${key}=${current[key]}`) + .map(key => `${key}=${current[key]}`) .join('&'); }; diff --git a/resources/scripts/plugins/usePermissions.ts b/resources/scripts/plugins/usePermissions.ts index 50550ad9fd..f3bb660f9b 100644 --- a/resources/scripts/plugins/usePermissions.ts +++ b/resources/scripts/plugins/usePermissions.ts @@ -2,7 +2,7 @@ import { ServerContext } from '@/state/server'; import { useDeepCompareMemo } from '@/plugins/useDeepCompareMemo'; export const usePermissions = (action: string | string[]): boolean[] => { - const userPermissions = ServerContext.useStoreState((state) => state.server.permissions); + const userPermissions = ServerContext.useStoreState(state => state.server.permissions); return useDeepCompareMemo(() => { if (userPermissions[0] === '*') { @@ -10,13 +10,13 @@ export const usePermissions = (action: string | string[]): boolean[] => { } return (Array.isArray(action) ? action : [action]).map( - (permission) => + permission => // Allows checking for any permission matching a name, for example files.* // will return if the user has any permission under the file.XYZ namespace. (permission.endsWith('.*') && - userPermissions.filter((p) => p.startsWith(permission.split('.')[0])).length > 0) || + userPermissions.filter(p => p.startsWith(permission.split('.')?.[0] ?? '')).length > 0) || // Otherwise just check if the entire permission exists in the array or not. - userPermissions.indexOf(permission) >= 0 + userPermissions.indexOf(permission) >= 0, ); }, [action, userPermissions]); }; diff --git a/resources/scripts/plugins/usePersistedState.ts b/resources/scripts/plugins/usePersistedState.ts index f5d247677c..bffcfb1f07 100644 --- a/resources/scripts/plugins/usePersistedState.ts +++ b/resources/scripts/plugins/usePersistedState.ts @@ -2,7 +2,7 @@ import { Dispatch, SetStateAction, useEffect, useState } from 'react'; export function usePersistedState( key: string, - defaultValue: S + defaultValue: S, ): [S | undefined, Dispatch>] { const [state, setState] = useState(() => { try { diff --git a/resources/scripts/plugins/useSWRKey.ts b/resources/scripts/plugins/useSWRKey.ts index 78e84fd77b..fe69ff4eb2 100644 --- a/resources/scripts/plugins/useSWRKey.ts +++ b/resources/scripts/plugins/useSWRKey.ts @@ -7,7 +7,7 @@ type Context = string | string[] | (string | number | null | {})[]; function useSWRKey(context: Context, prefix: string | null = null): string { const key = useDeepCompareMemo((): string => { - return (Array.isArray(context) ? context : [context]).map((value) => JSON.stringify(value)).join(':'); + return (Array.isArray(context) ? context : [context]).map(value => JSON.stringify(value)).join(':'); }, [context]); if (!key.trim().length) { @@ -18,13 +18,13 @@ function useSWRKey(context: Context, prefix: string | null = null): string { } function useServerSWRKey(context: Context): string { - const uuid = ServerContext.useStoreState((state) => state.server.data?.uuid); + const uuid = ServerContext.useStoreState(state => state.server.data?.uuid); return useSWRKey(context, `server:${uuid}`); } function useUserSWRKey(context: Context): string { - const uuid = useStoreState((state) => state.user.data?.uuid); + const uuid = useStoreState(state => state.user.data?.uuid); return useSWRKey(context, `user:${uuid}`); } diff --git a/resources/scripts/plugins/useWebsocketEvent.ts b/resources/scripts/plugins/useWebsocketEvent.ts index b746e38896..7d55c076cb 100644 --- a/resources/scripts/plugins/useWebsocketEvent.ts +++ b/resources/scripts/plugins/useWebsocketEvent.ts @@ -3,7 +3,7 @@ import { useEffect, useRef } from 'react'; import { SocketEvent } from '@/components/server/events'; const useWebsocketEvent = (event: SocketEvent, callback: (data: string) => void) => { - const { connected, instance } = ServerContext.useStoreState((state) => state.socket); + const { connected, instance } = ServerContext.useStoreState(state => state.socket); const savedCallback = useRef(null); useEffect(() => { diff --git a/resources/scripts/routers/AuthenticationRouter.tsx b/resources/scripts/routers/AuthenticationRouter.tsx index 27d13f11b5..580d7fbb0a 100644 --- a/resources/scripts/routers/AuthenticationRouter.tsx +++ b/resources/scripts/routers/AuthenticationRouter.tsx @@ -1,29 +1,23 @@ -import React from 'react'; -import { Route, Switch, useRouteMatch } from 'react-router-dom'; +import { Route, Routes, useNavigate } from 'react-router-dom'; + import LoginContainer from '@/components/auth/LoginContainer'; import ForgotPasswordContainer from '@/components/auth/ForgotPasswordContainer'; import ResetPasswordContainer from '@/components/auth/ResetPasswordContainer'; import LoginCheckpointContainer from '@/components/auth/LoginCheckpointContainer'; import { NotFound } from '@/components/elements/ScreenBlock'; -import { useHistory, useLocation } from 'react-router'; export default () => { - const history = useHistory(); - const location = useLocation(); - const { path } = useRouteMatch(); + const navigate = useNavigate(); return ( -

    - - - - - - - - history.push('/auth/login')} /> - - +
    + + } /> + } /> + } /> + } /> + navigate('/auth/login')} />} /> +
    ); }; diff --git a/resources/scripts/routers/DashboardRouter.tsx b/resources/scripts/routers/DashboardRouter.tsx index 4ad69c6521..f874b20e32 100644 --- a/resources/scripts/routers/DashboardRouter.tsx +++ b/resources/scripts/routers/DashboardRouter.tsx @@ -1,50 +1,47 @@ -import React from 'react'; -import { NavLink, Route, Switch } from 'react-router-dom'; +import { Suspense } from 'react'; +import { NavLink, Route, Routes, useLocation } from 'react-router-dom'; + import NavigationBar from '@/components/NavigationBar'; import DashboardContainer from '@/components/dashboard/DashboardContainer'; import { NotFound } from '@/components/elements/ScreenBlock'; -import TransitionRouter from '@/TransitionRouter'; -import SubNavigation from '@/components/elements/SubNavigation'; -import { useLocation } from 'react-router'; import Spinner from '@/components/elements/Spinner'; +import SubNavigation from '@/components/elements/SubNavigation'; import routes from '@/routers/routes'; -export default () => { +function DashboardRouter() { const location = useLocation(); return ( <> + {location.pathname.startsWith('/account') && (
    {routes.account - .filter((route) => !!route.name) - .map(({ path, name, exact = false }) => ( - + .filter(route => route.path !== undefined) + .map(({ path, name, end = false }) => ( + {name} ))}
    )} - - }> - - - - - {routes.account.map(({ path, component: Component }) => ( - - - - ))} - - - - - - + + }> + + } /> + + {routes.account.map(({ route, component: Component }) => ( + } /> + ))} + + } /> + + ); -}; +} + +export default DashboardRouter; diff --git a/resources/scripts/routers/ServerRouter.tsx b/resources/scripts/routers/ServerRouter.tsx index 2976cf789e..fa97dba9cf 100644 --- a/resources/scripts/routers/ServerRouter.tsx +++ b/resources/scripts/routers/ServerRouter.tsx @@ -1,11 +1,9 @@ import TransferListener from '@/components/server/TransferListener'; -import React, { useEffect, useState } from 'react'; -import { NavLink, Route, Switch, useRouteMatch } from 'react-router-dom'; +import { Fragment, useEffect, useState } from 'react'; +import { NavLink, Route, Routes, useParams } from 'react-router-dom'; import NavigationBar from '@/components/NavigationBar'; -import TransitionRouter from '@/TransitionRouter'; import WebsocketHandler from '@/components/server/WebsocketHandler'; import { ServerContext } from '@/state/server'; -import { CSSTransition } from 'react-transition-group'; import Can from '@/components/elements/Can'; import Spinner from '@/components/elements/Spinner'; import { NotFound, ServerError } from '@/components/elements/ScreenBlock'; @@ -21,38 +19,35 @@ import ConflictStateRenderer from '@/components/server/ConflictStateRenderer'; import PermissionRoute from '@/components/elements/PermissionRoute'; import routes from '@/routers/routes'; -export default () => { - const match = useRouteMatch<{ id: string }>(); +function ServerRouter() { + const params = useParams<'id'>(); const location = useLocation(); - const rootAdmin = useStoreState((state) => state.user.data!.rootAdmin); + const rootAdmin = useStoreState(state => state.user.data!.rootAdmin); const [error, setError] = useState(''); - const id = ServerContext.useStoreState((state) => state.server.data?.id); - const uuid = ServerContext.useStoreState((state) => state.server.data?.uuid); - const inConflictState = ServerContext.useStoreState((state) => state.server.inConflictState); - const serverId = ServerContext.useStoreState((state) => state.server.data?.internalId); - const getServer = ServerContext.useStoreActions((actions) => actions.server.getServer); - const clearServerState = ServerContext.useStoreActions((actions) => actions.clearServerState); - - const to = (value: string, url = false) => { - if (value === '/') { - return url ? match.url : match.path; - } - return `${(url ? match.url : match.path).replace(/\/*$/, '')}/${value.replace(/^\/+/, '')}`; - }; + const id = ServerContext.useStoreState(state => state.server.data?.id); + const uuid = ServerContext.useStoreState(state => state.server.data?.uuid); + const inConflictState = ServerContext.useStoreState(state => state.server.inConflictState); + const serverId = ServerContext.useStoreState(state => state.server.data?.internalId); + const getServer = ServerContext.useStoreActions(actions => actions.server.getServer); + const clearServerState = ServerContext.useStoreActions(actions => actions.clearServerState); useEffect( () => () => { clearServerState(); }, - [] + [], ); useEffect(() => { setError(''); - getServer(match.params.id).catch((error) => { + if (params.id === undefined) { + return; + } + + getServer(params.id).catch(error => { console.error(error); setError(httpErrorToHuman(error)); }); @@ -60,10 +55,10 @@ export default () => { return () => { clearServerState(); }; - }, [match.params.id]); + }, [params.id]); return ( - + {!uuid || !id ? ( error ? ( @@ -73,56 +68,61 @@ export default () => { ) ) : ( <> - - -
    - {routes.server - .filter((route) => !!route.name) - .map((route) => - route.permission ? ( - - - {route.name} - - - ) : ( - + +
    + {routes.server + .filter(route => route.path !== undefined) + .map(route => + route.permission ? ( + + {route.name} - ) - )} - {rootAdmin && ( - // eslint-disable-next-line react/jsx-no-target-blank - - - + + ) : ( + + {route.name} + + ), )} -
    -
    - + {rootAdmin && ( + // eslint-disable-next-line react/jsx-no-target-blank + + + + )} +
    +
    - {inConflictState && (!rootAdmin || (rootAdmin && !location.pathname.endsWith(`/server/${id}`))) ? ( + {inConflictState && (!rootAdmin || (rootAdmin && !location.pathname.endsWith(`/server/${id}/`))) ? ( ) : ( - - - {routes.server.map(({ path, permission, component: Component }) => ( - - - - - - ))} - - - + + {routes.server.map(({ route, permission, component: Component }) => ( + + + + + + } + /> + ))} + + } /> + )} )} -
    + ); -}; +} + +export default ServerRouter; diff --git a/resources/scripts/routers/routes.ts b/resources/scripts/routers/routes.ts index 8fba642212..5f1da1fe73 100644 --- a/resources/scripts/routers/routes.ts +++ b/resources/scripts/routers/routes.ts @@ -1,4 +1,6 @@ -import React, { lazy } from 'react'; +import type { ComponentType } from 'react'; +import { lazy } from 'react'; + import ServerConsole from '@/components/server/console/ServerConsoleContainer'; import DatabasesContainer from '@/components/server/databases/DatabasesContainer'; import ScheduleContainer from '@/components/server/schedules/ScheduleContainer'; @@ -15,7 +17,7 @@ import ActivityLogContainer from '@/components/dashboard/activity/ActivityLogCon import ServerActivityLogContainer from '@/components/server/ServerActivityLogContainer'; // Each of the router files is already code split out appropriately — so -// all of the items above will only be loaded in when that router is loaded. +// all the items above will only be loaded in when that router is loaded. // // These specific lazy loaded routes are to avoid loading in heavy screens // for the server dashboard when they're only needed for specific instances. @@ -23,119 +25,147 @@ const FileEditContainer = lazy(() => import('@/components/server/files/FileEditC const ScheduleEditContainer = lazy(() => import('@/components/server/schedules/ScheduleEditContainer')); interface RouteDefinition { - path: string; + /** + * Route is the path that will be matched against, this field supports wildcards. + */ + route: string; + /** + * Path is the path that will be used for any navbars or links, do not use wildcards or fancy + * matchers here. If this field is left undefined, this route will not have a navigation element, + */ + path?: string; // If undefined is passed this route is still rendered into the router itself // but no navigation link is displayed in the sub-navigation menu. name: string | undefined; - component: React.ComponentType; - exact?: boolean; + component: ComponentType; + end?: boolean; } interface ServerRouteDefinition extends RouteDefinition { - permission: string | string[] | null; + permission?: string | string[]; } interface Routes { - // All of the routes available under "/account" + // All the routes available under "/account" account: RouteDefinition[]; - // All of the routes available under "/server/:id" + // All the routes available under "/server/:id" server: ServerRouteDefinition[]; } export default { account: [ { - path: '/', + route: '', + path: '', name: 'Account', component: AccountOverviewContainer, - exact: true, + end: true, }, { - path: '/api', + route: 'api', + path: 'api', name: 'API Credentials', component: AccountApiContainer, }, { - path: '/ssh', + route: 'ssh', + path: 'ssh', name: 'SSH Keys', component: AccountSSHContainer, }, { - path: '/activity', + route: 'activity', + path: 'activity', name: 'Activity', component: ActivityLogContainer, }, ], server: [ { - path: '/', + route: '', + path: '', permission: null, name: 'Console', component: ServerConsole, - exact: true, + end: true, }, { - path: '/files', + route: 'files/*', + path: 'files', permission: 'file.*', name: 'Files', component: FileManagerContainer, }, { - path: '/files/:action(edit|new)', + route: 'files/edit/*', + permission: 'file.*', + name: undefined, + component: FileEditContainer, + }, + { + route: 'files/new/*', permission: 'file.*', name: undefined, component: FileEditContainer, }, { - path: '/databases', + route: 'databases/*', + path: 'databases', permission: 'database.*', name: 'Databases', component: DatabasesContainer, }, { - path: '/schedules', + route: 'schedules/*', + path: 'schedules', permission: 'schedule.*', name: 'Schedules', component: ScheduleContainer, }, { - path: '/schedules/:id', + route: 'schedules/:id/*', permission: 'schedule.*', name: undefined, component: ScheduleEditContainer, }, { - path: '/users', + route: 'users/*', + path: 'users', permission: 'user.*', name: 'Users', component: UsersContainer, }, { - path: '/backups', + route: 'backups/*', + path: 'backups', permission: 'backup.*', name: 'Backups', component: BackupContainer, }, { - path: '/network', + route: 'network/*', + path: 'network', permission: 'allocation.*', name: 'Network', component: NetworkContainer, }, { - path: '/startup', + route: 'startup/*', + path: 'startup', permission: 'startup.*', name: 'Startup', component: StartupContainer, }, { - path: '/settings', + route: 'settings/*', + path: 'settings', permission: ['settings.*', 'file.sftp'], name: 'Settings', component: SettingsContainer, }, { - path: '/activity', + route: 'activity/*', + path: 'activity', permission: 'activity.*', name: 'Activity', component: ServerActivityLogContainer, diff --git a/resources/scripts/setup-tests.ts b/resources/scripts/setup-tests.ts deleted file mode 100644 index 7b0828bfa8..0000000000 --- a/resources/scripts/setup-tests.ts +++ /dev/null @@ -1 +0,0 @@ -import '@testing-library/jest-dom'; diff --git a/resources/scripts/state/flashes.ts b/resources/scripts/state/flashes.ts index 088aca2f01..9585bb084c 100644 --- a/resources/scripts/state/flashes.ts +++ b/resources/scripts/state/flashes.ts @@ -47,7 +47,7 @@ const flashes: FlashStore = { }), clearFlashes: action((state, payload) => { - state.items = payload ? state.items.filter((flashes) => flashes.key !== payload) : []; + state.items = payload ? state.items.filter(flashes => flashes.key !== payload) : []; }), }; diff --git a/resources/scripts/state/permissions.ts b/resources/scripts/state/permissions.ts index 2e1fc01448..41579b5862 100644 --- a/resources/scripts/state/permissions.ts +++ b/resources/scripts/state/permissions.ts @@ -21,7 +21,7 @@ const permissions: GloablPermissionsStore = { state.data = payload; }), - getPermissions: thunk(async (actions) => { + getPermissions: thunk(async actions => { const permissions = await getSystemPermissions(); actions.setPermissions(permissions); diff --git a/resources/scripts/state/progress.ts b/resources/scripts/state/progress.ts index 530083d0f2..98f7d1de27 100644 --- a/resources/scripts/state/progress.ts +++ b/resources/scripts/state/progress.ts @@ -13,7 +13,7 @@ const progress: ProgressStore = { continuous: false, progress: undefined, - startContinuous: action((state) => { + startContinuous: action(state => { state.continuous = true; }), @@ -21,7 +21,7 @@ const progress: ProgressStore = { state.progress = payload; }), - setComplete: action((state) => { + setComplete: action(state => { if (state.progress) { state.progress = 100; } diff --git a/resources/scripts/state/server/databases.ts b/resources/scripts/state/server/databases.ts index 46f37ea762..211275fe9c 100644 --- a/resources/scripts/state/server/databases.ts +++ b/resources/scripts/state/server/databases.ts @@ -16,15 +16,15 @@ const databases: ServerDatabaseStore = { }), appendDatabase: action((state, payload) => { - if (state.data.find((database) => database.id === payload.id)) { - state.data = state.data.map((database) => (database.id === payload.id ? payload : database)); + if (state.data.find(database => database.id === payload.id)) { + state.data = state.data.map(database => (database.id === payload.id ? payload : database)); } else { state.data = [...state.data, payload]; } }), removeDatabase: action((state, payload) => { - state.data = [...state.data.filter((database) => database.id !== payload)]; + state.data = [...state.data.filter(database => database.id !== payload)]; }), }; diff --git a/resources/scripts/state/server/files.ts b/resources/scripts/state/server/files.ts index 7e4786ae4c..2ca3386389 100644 --- a/resources/scripts/state/server/files.ts +++ b/resources/scripts/state/server/files.ts @@ -1,13 +1,15 @@ -import { action, Action } from 'easy-peasy'; +import type { Action } from 'easy-peasy'; +import { action } from 'easy-peasy'; + import { cleanDirectoryPath } from '@/helpers'; -export interface FileUploadData { +interface FileUploadData { loaded: number; readonly abort: AbortController; readonly total: number; } -export interface ServerFileStore { +interface ServerFileStore { directory: string; selectedFiles: string[]; uploads: Record; @@ -37,15 +39,15 @@ const files: ServerFileStore = { }), appendSelectedFile: action((state, payload) => { - state.selectedFiles = state.selectedFiles.filter((f) => f !== payload).concat(payload); + state.selectedFiles = state.selectedFiles.filter(f => f !== payload).concat(payload); }), removeSelectedFile: action((state, payload) => { - state.selectedFiles = state.selectedFiles.filter((f) => f !== payload); + state.selectedFiles = state.selectedFiles.filter(f => f !== payload); }), - clearFileUploads: action((state) => { - Object.values(state.uploads).forEach((upload) => upload.abort.abort()); + clearFileUploads: action(state => { + Object.values(state.uploads).forEach(upload => upload.abort.abort()); state.uploads = {}; }), @@ -55,20 +57,27 @@ const files: ServerFileStore = { }), setUploadProgress: action((state, { name, loaded }) => { - if (state.uploads[name]) { - state.uploads[name].loaded = loaded; + const upload = state.uploads[name]; + if (upload === undefined) { + return; } + + upload.loaded = loaded; }), removeFileUpload: action((state, payload) => { - if (state.uploads[payload]) { - // Abort the request if it is still in flight. If it already completed this is - // a no-op. - state.uploads[payload].abort.abort(); - - delete state.uploads[payload]; + const upload = state.uploads[payload]; + if (upload === undefined) { + return; } + + // Abort the request if it is still in flight. If it already completed this is + // a no-op. + upload.abort.abort(); + + delete state.uploads[payload]; }), }; +export type { FileUploadData, ServerFileStore }; export default files; diff --git a/resources/scripts/state/server/index.ts b/resources/scripts/state/server/index.ts index f9806b6498..87d2088b0d 100644 --- a/resources/scripts/state/server/index.ts +++ b/resources/scripts/state/server/index.ts @@ -1,13 +1,20 @@ -import getServer, { Server } from '@/api/server/getServer'; -import { action, Action, computed, Computed, createContextStore, thunk, Thunk } from 'easy-peasy'; -import socket, { SocketStore } from './socket'; -import files, { ServerFileStore } from '@/state/server/files'; -import subusers, { ServerSubuserStore } from '@/state/server/subusers'; -import { composeWithDevTools } from 'redux-devtools-extension'; -import schedules, { ServerScheduleStore } from '@/state/server/schedules'; -import databases, { ServerDatabaseStore } from '@/state/server/databases'; +import type { Action, Computed, Thunk } from 'easy-peasy'; +import { action, computed, createContextStore, thunk } from 'easy-peasy'; import isEqual from 'react-fast-compare'; +import type { Server } from '@/api/server/getServer'; +import getServer from '@/api/server/getServer'; +import type { ServerDatabaseStore } from '@/state/server/databases'; +import databases from '@/state/server/databases'; +import type { ServerFileStore } from '@/state/server/files'; +import files from '@/state/server/files'; +import type { ServerScheduleStore } from '@/state/server/schedules'; +import schedules from '@/state/server/schedules'; +import type { SocketStore } from '@/state/server/socket'; +import socket from '@/state/server/socket'; +import type { ServerSubuserStore } from '@/state/server/subusers'; +import subusers from '@/state/server/subusers'; + export type ServerStatus = 'offline' | 'starting' | 'stopping' | 'running' | null; interface ServerDataStore { @@ -25,7 +32,7 @@ interface ServerDataStore { const server: ServerDataStore = { permissions: [], - inConflictState: computed((state) => { + inConflictState: computed(state => { if (!state.data) { return false; } @@ -33,7 +40,7 @@ const server: ServerDataStore = { return state.data.status !== null || state.data.isTransferring || state.data.isNodeUnderMaintenance; }), - isInstalling: computed((state) => { + isInstalling: computed(state => { return state.data?.status === 'installing' || state.data?.status === 'install_failed'; }), @@ -87,37 +94,29 @@ export interface ServerStore { clearServerState: Action; } -export const ServerContext = createContextStore( - { - server, - socket, - status, - databases, - files, - subusers, - schedules, - clearServerState: action((state) => { - state.server.data = undefined; - state.server.permissions = []; - state.databases.data = []; - state.subusers.data = []; - state.files.directory = '/'; - state.files.selectedFiles = []; - state.schedules.data = []; - - if (state.socket.instance) { - state.socket.instance.removeAllListeners(); - state.socket.instance.close(); - } - - state.socket.instance = null; - state.socket.connected = false; - }), - }, - { - compose: composeWithDevTools({ - name: 'ServerStore', - trace: true, - }), - } -); +export const ServerContext = createContextStore({ + server, + socket, + status, + databases, + files, + subusers, + schedules, + clearServerState: action(state => { + state.server.data = undefined; + state.server.permissions = []; + state.databases.data = []; + state.subusers.data = []; + state.files.directory = '/'; + state.files.selectedFiles = []; + state.schedules.data = []; + + if (state.socket.instance) { + state.socket.instance.removeAllListeners(); + state.socket.instance.close(); + } + + state.socket.instance = null; + state.socket.connected = false; + }), +}); diff --git a/resources/scripts/state/server/schedules.ts b/resources/scripts/state/server/schedules.ts index 416cc8a49e..f4e73ac755 100644 --- a/resources/scripts/state/server/schedules.ts +++ b/resources/scripts/state/server/schedules.ts @@ -16,15 +16,15 @@ const schedules: ServerScheduleStore = { }), appendSchedule: action((state, payload) => { - if (state.data.find((schedule) => schedule.id === payload.id)) { - state.data = state.data.map((schedule) => (schedule.id === payload.id ? payload : schedule)); + if (state.data.find(schedule => schedule.id === payload.id)) { + state.data = state.data.map(schedule => (schedule.id === payload.id ? payload : schedule)); } else { state.data = [...state.data, payload]; } }), removeSchedule: action((state, payload) => { - state.data = [...state.data.filter((schedule) => schedule.id !== payload)]; + state.data = [...state.data.filter(schedule => schedule.id !== payload)]; }), }; diff --git a/resources/scripts/state/server/subusers.ts b/resources/scripts/state/server/subusers.ts index e727211f4d..64359b06ec 100644 --- a/resources/scripts/state/server/subusers.ts +++ b/resources/scripts/state/server/subusers.ts @@ -60,7 +60,7 @@ const subusers: ServerSubuserStore = { let matched = false; state.data = [ ...state.data - .map((user) => { + .map(user => { if (user.uuid === payload.uuid) { matched = true; @@ -74,7 +74,7 @@ const subusers: ServerSubuserStore = { }), removeSubuser: action((state, payload) => { - state.data = [...state.data.filter((user) => user.uuid !== payload)]; + state.data = [...state.data.filter(user => user.uuid !== payload)]; }), }; diff --git a/resources/scripts/theme.ts b/resources/scripts/theme.ts index 8679ebaa75..737d5225ca 100644 --- a/resources/scripts/theme.ts +++ b/resources/scripts/theme.ts @@ -1,4 +1,5 @@ -import { BreakpointFunction, createBreakpoint } from 'styled-components-breakpoint'; +import type { BreakpointFunction } from 'styled-components-breakpoint'; +import { createBreakpoint } from 'styled-components-breakpoint'; type Breakpoints = 'xs' | 'sm' | 'md' | 'lg' | 'xl'; export const breakpoint: BreakpointFunction = createBreakpoint({ diff --git a/resources/views/templates/wrapper.blade.php b/resources/views/templates/wrapper.blade.php index 1105a0724d..8624d2e853 100644 --- a/resources/views/templates/wrapper.blade.php +++ b/resources/views/templates/wrapper.blade.php @@ -1,5 +1,5 @@ - + {{ config('app.name', 'Pterodactyl') }} @@ -39,6 +39,9 @@ @yield('assets') @include('layouts.scripts') + + @viteReactRefresh + @vite('resources/scripts/index.tsx') @section('content') @@ -46,8 +49,5 @@ @yield('container') @yield('below-container') @show - @section('scripts') - {!! $asset->js('main.js') !!} - @show diff --git a/shell.nix b/shell.nix index afe21af5e1..682c88f421 100644 --- a/shell.nix +++ b/shell.nix @@ -1,17 +1,14 @@ -{pkgs ? import {}}: +{ + pkgs ? import {}, + php81WithExtensions, +}: with pkgs; mkShell rec { buildInputs = [ alejandra - (php81.buildEnv { - extensions = ({ enabled, all }: enabled ++ (with all; [ - redis - xdebug - ])); - extraConfig = '' - xdebug.mode=debug - ''; - }) - php81Packages.composer + nodejs-18_x + nodePackages.yarn + php81WithExtensions + (php81Packages.composer.override {php = php81WithExtensions;}) ]; } diff --git a/tailwind.config.js b/tailwind.config.js index 7c814bd61b..dd5e175300 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -14,9 +14,7 @@ const gray = { }; module.exports = { - content: [ - './resources/scripts/**/*.{js,ts,tsx}', - ], + content: ['./resources/scripts/**/*.{js,ts,tsx}'], theme: { extend: { fontFamily: { @@ -47,5 +45,5 @@ module.exports = { require('@tailwindcss/forms')({ strategy: 'class', }), - ] + ], }; diff --git a/tsconfig.json b/tsconfig.json index 578a73e315..4e3d499234 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,47 +1,55 @@ { - "compilerOptions": { - "target": "es2015", - "module": "es2020", - "jsx": "react", - "moduleResolution": "node", - "lib": [ - "es2015", - "dom" - ], - "strict": true, - "noEmit": true, - "sourceMap": true, - "noImplicitReturns": true, - "skipLibCheck": true, - "skipDefaultLibCheck": true, - "esModuleInterop": true, - "allowSyntheticDefaultImports": true, - "baseUrl": ".", - "importsNotUsedAsValues": "preserve", - "paths": { - "@/*": [ - "./resources/scripts/*" - ], - "@definitions/*": [ - "./resources/scripts/api/definitions/*" - ], - "@feature/*": [ - "./resources/scripts/components/server/features/*" - ] + "$schema": "https://json.schemastore.org/tsconfig", + "display": "Default", + "compilerOptions": { + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "jsx": "react-jsx", + "module": "ESNext", + "target": "ES2019", + "paths": { + "@/*": [ + "./resources/scripts/*" + ], + "@definitions/*": [ + "./resources/scripts/api/definitions/*" + ], + "@feature/*": [ + "./resources/scripts/components/server/features/*" + ] + }, + "plugins": [ + { + "name": "typescript-plugin-tw-template" + } + ], + + "allowJs": false, + "allowSyntheticDefaultImports": true, + "composite": false, + "declaration": true, + "declarationMap": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "inlineSources": false, + "isolatedModules": true, + "moduleResolution": "Node", + "newLine": "lf", + "noEmit": true, + "noFallthroughCasesInSwitch": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noPropertyAccessFromIndexSignature": false, + "noUncheckedIndexedAccess": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "preserveWatchOutput": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "sourceMap": true, + "strict": true, + "stripInternal": true, + "useDefineForClassFields": true }, - "plugins": [ - { - "name": "typescript-plugin-tw-template" - } - ], - "typeRoots": [ - "node_modules/@types" - ] - }, - "include": [ - "./resources/scripts/**/*" - ], - "exclude": [ - "/node_modules/" - ] + "include": ["./resources/scripts/**/*", "vite.config.ts"], + "exclude": ["node_modules"] } diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000000..cd326f6e59 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,62 @@ +/// +import react from '@vitejs/plugin-react'; +import laravel from 'laravel-vite-plugin'; +import { dirname, resolve } from 'pathe'; +import { fileURLToPath } from 'node:url'; +import { defineConfig } from 'vite'; + +const plugins = [ + react({ + babel: { + plugins: ['babel-plugin-macros', 'babel-plugin-styled-components'], + }, + }), +]; + +if (process.env.VITEST === undefined) { + plugins.push( + laravel({ + input: 'resources/scripts/index.tsx', + }), + ); +} + +export default defineConfig({ + define: + process.env.VITEST === undefined + ? { + 'process.env': {}, + 'process.platform': null, + 'process.version': null, + 'process.versions': null, + } + : undefined, + + plugins, + + resolve: { + alias: { + '@': resolve(dirname(fileURLToPath(import.meta.url)), 'resources', 'scripts'), + '@definitions': resolve( + dirname(fileURLToPath(import.meta.url)), + 'resources', + 'scripts', + 'api', + 'definitions', + ), + '@feature': resolve( + dirname(fileURLToPath(import.meta.url)), + 'resources', + 'scripts', + 'components', + 'server', + 'features', + ), + }, + }, + + test: { + environment: 'happy-dom', + include: ['resources/scripts/**/*.{spec,test}.{ts,tsx}'], + }, +}); diff --git a/webpack.config.js b/webpack.config.js deleted file mode 100644 index aa240fbd6a..0000000000 --- a/webpack.config.js +++ /dev/null @@ -1,156 +0,0 @@ -const path = require('path'); -const webpack = require('webpack'); -const AssetsManifestPlugin = require('webpack-assets-manifest'); -const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); -const TerserPlugin = require('terser-webpack-plugin'); -const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; - -const isProduction = process.env.NODE_ENV === 'production'; - -module.exports = { - cache: true, - target: 'web', - mode: process.env.NODE_ENV, - devtool: isProduction ? false : (process.env.DEVTOOL || 'eval-source-map'), - performance: { - hints: false, - }, - entry: ['react-hot-loader/patch', './resources/scripts/index.tsx'], - output: { - path: path.join(__dirname, '/public/assets'), - filename: isProduction ? 'bundle.[chunkhash:8].js' : 'bundle.[hash:8].js', - chunkFilename: isProduction ? '[name].[chunkhash:8].js' : '[name].[hash:8].js', - publicPath: (process.env.WEBPACK_PUBLIC_PATH || '/assets/'), - crossOriginLoading: 'anonymous', - }, - module: { - rules: [ - { - test: /\.tsx?$/, - exclude: /node_modules|\.spec\.tsx?$/, - loader: 'babel-loader', - }, - { - test: /\.mjs$/, - include: /node_modules/, - type: 'javascript/auto', - }, - { - test: /\.css$/, - use: [ - { loader: 'style-loader' }, - { - loader: 'css-loader', - options: { - modules: { - auto: true, - localIdentName: isProduction ? '[name]_[hash:base64:8]' : '[path][name]__[local]', - localIdentContext: path.join(__dirname, 'resources/scripts/components'), - }, - sourceMap: !isProduction, - importLoaders: 1, - }, - }, - { - loader: 'postcss-loader', - options: { sourceMap: !isProduction }, - }, - ], - }, - { - test: /\.(png|jp(e?)g|gif)$/, - loader: 'file-loader', - options: { - name: 'images/[name].[hash:8].[ext]', - }, - }, - { - test: /\.svg$/, - loader: 'svg-url-loader', - }, - { - test: /\.js$/, - enforce: 'pre', - loader: 'source-map-loader', - } - ], - }, - stats: { - // Ignore warnings emitted by "source-map-loader" when trying to parse source maps from - // JS plugins we use, namely brace editor. - warningsFilter: [/Failed to parse source map/], - }, - resolve: { - extensions: ['.ts', '.tsx', '.js', '.json'], - alias: { - '@': path.join(__dirname, '/resources/scripts'), - '@definitions': path.join(__dirname, '/resources/scripts/api/definitions'), - '@feature': path.join(__dirname, '/resources/scripts/components/server/features'), - }, - symlinks: false, - }, - externals: { - // Mark moment as an external to exclude it from the Chart.js build since we don't need to use - // it for anything. - moment: 'moment', - }, - plugins: [ - new webpack.EnvironmentPlugin({ - NODE_ENV: 'development', - DEBUG: process.env.NODE_ENV !== 'production', - WEBPACK_BUILD_HASH: Date.now().toString(16), - }), - new AssetsManifestPlugin({ writeToDisk: true, publicPath: true, integrity: true, integrityHashes: ['sha384'] }), - 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', - analyzerPort: 8081, - }) : null - ].filter(p => p), - optimization: { - usedExports: true, - sideEffects: false, - runtimeChunk: false, - removeEmptyChunks: true, - minimize: isProduction, - minimizer: [ - new TerserPlugin({ - cache: isProduction, - parallel: true, - extractComments: false, - terserOptions: { - mangle: true, - output: { - comments: false, - }, - }, - }), - ], - }, - watchOptions: { - poll: 1000, - ignored: /node_modules/, - }, - devServer: { - compress: true, - contentBase: path.join(__dirname, '/public'), - publicPath: process.env.WEBPACK_PUBLIC_PATH || '/assets/', - allowedHosts: [ - '.pterodactyl.test', - ], - headers: { - 'Access-Control-Allow-Origin': '*', - }, - }, -}; diff --git a/yarn.lock b/yarn.lock index 3142ac7558..8c7463126a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10,47 +10,47 @@ "@jridgewell/gen-mapping" "^0.1.0" "@jridgewell/trace-mapping" "^0.3.9" -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.8.3": +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.18.6.tgz#3b25d38c89600baa2dcc219edfa88a74eb2c427a" integrity sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q== dependencies: "@babel/highlight" "^7.18.6" -"@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.16.7": +"@babel/code-frame@^7.10.4", "@babel/code-frame@^7.16.7": version "7.16.7" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.16.7.tgz#44416b6bd7624b998f5b1af5d470856c40138789" integrity sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg== dependencies: "@babel/highlight" "^7.16.7" -"@babel/compat-data@^7.12.1", "@babel/compat-data@^7.17.10": - version "7.18.5" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.18.5.tgz#acac0c839e317038c73137fbb6ef71a1d6238471" - integrity sha512-BxhE40PVCBxVEJsSBhB6UWyAuqJRxGsAw8BdHMJ3AKGydcwuWW4kOO3HmqBQAdcq/OP+/DlTVxLvsCzRTnZuGg== +"@babel/compat-data@^7.19.3": + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.19.4.tgz#95c86de137bf0317f3a570e1b6e996b427299747" + integrity sha512-CHIGpJcUQ5lU9KrPHTjBMhVwQG6CQjxfg36fGXl3qk/Gik1WwWachaXFuo0uCWJT/mStOKtcbFJCaVLihC1CMw== -"@babel/core@^7.11.6", "@babel/core@^7.12.1", "@babel/core@^7.12.3": - version "7.18.5" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.18.5.tgz#c597fa680e58d571c28dda9827669c78cdd7f000" - integrity sha512-MGY8vg3DxMnctw0LdvSEojOsumc70g0t18gNyUdAZqB1Rpd1Bqo/svHGvt+UJ6JcGX+DIekGFDxxIWofBxLCnQ== +"@babel/core@^7.19.6": + version "7.19.6" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.19.6.tgz#7122ae4f5c5a37c0946c066149abd8e75f81540f" + integrity sha512-D2Ue4KHpc6Ys2+AxpIx1BZ8+UegLLLE2p3KJEuJRKmokHOtl49jQ5ny1773KsGLZs8MQvBidAF6yWUJxRqtKtg== dependencies: "@ampproject/remapping" "^2.1.0" - "@babel/code-frame" "^7.16.7" - "@babel/generator" "^7.18.2" - "@babel/helper-compilation-targets" "^7.18.2" - "@babel/helper-module-transforms" "^7.18.0" - "@babel/helpers" "^7.18.2" - "@babel/parser" "^7.18.5" - "@babel/template" "^7.16.7" - "@babel/traverse" "^7.18.5" - "@babel/types" "^7.18.4" + "@babel/code-frame" "^7.18.6" + "@babel/generator" "^7.19.6" + "@babel/helper-compilation-targets" "^7.19.3" + "@babel/helper-module-transforms" "^7.19.6" + "@babel/helpers" "^7.19.4" + "@babel/parser" "^7.19.6" + "@babel/template" "^7.18.10" + "@babel/traverse" "^7.19.6" + "@babel/types" "^7.19.4" convert-source-map "^1.7.0" debug "^4.1.0" gensync "^1.0.0-beta.2" json5 "^2.2.1" semver "^6.3.0" -"@babel/generator@^7.18.2", "@babel/generator@^7.7.2": +"@babel/generator@^7.18.2": version "7.18.2" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.18.2.tgz#33873d6f89b21efe2da63fe554460f3df1c5880d" integrity sha512-W1lG5vUwFvfMd8HVXqdfbuG7RuaSrTCCD8cl8fP8wOivdbtbIg2Db3IWUcgvfxKbbn6ZBGYRW/Zk1MIwK49mgw== @@ -59,90 +59,50 @@ "@jridgewell/gen-mapping" "^0.3.0" jsesc "^2.5.1" -"@babel/helper-annotate-as-pure@^7.10.4", "@babel/helper-annotate-as-pure@^7.16.0": +"@babel/generator@^7.19.6": + version "7.19.6" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.19.6.tgz#9e481a3fe9ca6261c972645ae3904ec0f9b34a1d" + integrity sha512-oHGRUQeoX1QrKeJIKVe0hwjGqNnVYsM5Nep5zo0uE0m42sLH+Fsd2pStJ5sRM1bNyTUUoz0pe2lTeMJrb/taTA== + dependencies: + "@babel/types" "^7.19.4" + "@jridgewell/gen-mapping" "^0.3.2" + jsesc "^2.5.1" + +"@babel/helper-annotate-as-pure@^7.16.0": version "7.16.7" resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz#bb2339a7534a9c128e3102024c60760a3a7f3862" integrity sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw== dependencies: "@babel/types" "^7.16.7" -"@babel/helper-builder-binary-assignment-operator-visitor@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.10.4.tgz#bb0b75f31bf98cbf9ff143c1ae578b87274ae1a3" - integrity sha512-L0zGlFrGWZK4PbT8AszSfLTM5sDU1+Az/En9VrdT8/LmEiJt4zXt+Jve9DCAnQcbqDhCI+29y/L93mrDzddCcg== - dependencies: - "@babel/helper-explode-assignable-expression" "^7.10.4" - "@babel/types" "^7.10.4" - -"@babel/helper-builder-react-jsx-experimental@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/helper-builder-react-jsx-experimental/-/helper-builder-react-jsx-experimental-7.12.1.tgz#1f1ad4c95f1d059856d2fdbc0763489d020cd02d" - integrity sha512-82to8lR7TofZWbTd3IEZT1xNHfeU/Ef4rDm/GLXddzqDh+yQ19QuGSzqww51aNxVH8rwfRIzL0EUQsvODVhtyw== - dependencies: - "@babel/helper-annotate-as-pure" "^7.10.4" - "@babel/helper-module-imports" "^7.12.1" - "@babel/types" "^7.12.1" - -"@babel/helper-builder-react-jsx@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/helper-builder-react-jsx/-/helper-builder-react-jsx-7.10.4.tgz#8095cddbff858e6fa9c326daee54a2f2732c1d5d" - integrity sha512-5nPcIZ7+KKDxT1427oBivl9V9YTal7qk0diccnh7RrcgrT/pGFOjgGw1dgryyx1GvHEpXVfoDF6Ak3rTiWh8Rg== +"@babel/helper-annotate-as-pure@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz#eaa49f6f80d5a33f9a5dd2276e6d6e451be0a6bb" + integrity sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA== dependencies: - "@babel/helper-annotate-as-pure" "^7.10.4" - "@babel/types" "^7.10.4" + "@babel/types" "^7.18.6" -"@babel/helper-compilation-targets@^7.12.1", "@babel/helper-compilation-targets@^7.18.2": - version "7.18.2" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.18.2.tgz#67a85a10cbd5fc7f1457fec2e7f45441dc6c754b" - integrity sha512-s1jnPotJS9uQnzFtiZVBUxe67CuBa679oWFHpxYYnTpRL/1ffhyX44R9uYiXoa/pLXcY9H2moJta0iaanlk/rQ== +"@babel/helper-compilation-targets@^7.19.3": + version "7.19.3" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.19.3.tgz#a10a04588125675d7c7ae299af86fa1b2ee038ca" + integrity sha512-65ESqLGyGmLvgR0mst5AdW1FkNlj9rQsCKduzEoEPhBCDFGXvz2jW6bXFG6i0/MrV2s7hhXjjb2yAzcPuQlLwg== dependencies: - "@babel/compat-data" "^7.17.10" - "@babel/helper-validator-option" "^7.16.7" - browserslist "^4.20.2" + "@babel/compat-data" "^7.19.3" + "@babel/helper-validator-option" "^7.18.6" + browserslist "^4.21.3" semver "^6.3.0" -"@babel/helper-create-class-features-plugin@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.12.1.tgz#3c45998f431edd4a9214c5f1d3ad1448a6137f6e" - integrity sha512-hkL++rWeta/OVOBTRJc9a5Azh5mt5WgZUGAKMD8JM141YsE08K//bp1unBBieO6rUKkIPyUE0USQ30jAy3Sk1w== - dependencies: - "@babel/helper-function-name" "^7.10.4" - "@babel/helper-member-expression-to-functions" "^7.12.1" - "@babel/helper-optimise-call-expression" "^7.10.4" - "@babel/helper-replace-supers" "^7.12.1" - "@babel/helper-split-export-declaration" "^7.10.4" - -"@babel/helper-create-regexp-features-plugin@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.12.1.tgz#18b1302d4677f9dc4740fe8c9ed96680e29d37e8" - integrity sha512-rsZ4LGvFTZnzdNZR5HZdmJVuXK8834R5QkF3WvcnBhrlVtF0HSIUC6zbreL9MgjTywhKokn8RIYRiq99+DLAxA== - dependencies: - "@babel/helper-annotate-as-pure" "^7.10.4" - "@babel/helper-regex" "^7.10.4" - regexpu-core "^4.7.1" - -"@babel/helper-define-map@^7.10.4": - version "7.10.5" - resolved "https://registry.yarnpkg.com/@babel/helper-define-map/-/helper-define-map-7.10.5.tgz#b53c10db78a640800152692b13393147acb9bb30" - integrity sha512-fMw4kgFB720aQFXSVaXr79pjjcW5puTCM16+rECJ/plGS+zByelE8l9nCpV1GibxTnFVmUuYG9U8wYfQHdzOEQ== - dependencies: - "@babel/helper-function-name" "^7.10.4" - "@babel/types" "^7.10.5" - lodash "^4.17.19" - -"@babel/helper-environment-visitor@^7.16.7", "@babel/helper-environment-visitor@^7.18.2": +"@babel/helper-environment-visitor@^7.18.2": version "7.18.2" resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.2.tgz#8a6d2dedb53f6bf248e31b4baf38739ee4a637bd" integrity sha512-14GQKWkX9oJzPiQQ7/J36FTXcD4kSp8egKjO9nINlSKiHITRA9q/R74qu8S9xlc/b/yjsJItQUeeh3xnGN0voQ== -"@babel/helper-explode-assignable-expression@^7.10.4": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.12.1.tgz#8006a466695c4ad86a2a5f2fb15b5f2c31ad5633" - integrity sha512-dmUwH8XmlrUpVqgtZ737tK88v07l840z9j3OEhCLwKTkjlvKpfqXVIZ0wpK3aeOxspwGrf/5AP5qLx4rO3w5rA== - dependencies: - "@babel/types" "^7.12.1" +"@babel/helper-environment-visitor@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz#0c0cee9b35d2ca190478756865bb3528422f51be" + integrity sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg== -"@babel/helper-function-name@^7.10.4", "@babel/helper-function-name@^7.17.9": +"@babel/helper-function-name@^7.17.9": version "7.17.9" resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.17.9.tgz#136fcd54bc1da82fcb47565cf16fd8e444b1ff12" integrity sha512-7cRisGlVtiVqZ0MW0/yFB4atgpGLWEHUVYnb448hZK4x+vih0YO5UoS11XIYtZYqHd0dIPMdUSv8q5K4LdMnIg== @@ -150,133 +110,110 @@ "@babel/template" "^7.16.7" "@babel/types" "^7.17.0" -"@babel/helper-hoist-variables@^7.10.4", "@babel/helper-hoist-variables@^7.16.7": +"@babel/helper-function-name@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz#941574ed5390682e872e52d3f38ce9d1bef4648c" + integrity sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w== + dependencies: + "@babel/template" "^7.18.10" + "@babel/types" "^7.19.0" + +"@babel/helper-hoist-variables@^7.16.7": version "7.16.7" resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz#86bcb19a77a509c7b77d0e22323ef588fa58c246" integrity sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg== dependencies: "@babel/types" "^7.16.7" -"@babel/helper-member-expression-to-functions@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.12.1.tgz#fba0f2fcff3fba00e6ecb664bb5e6e26e2d6165c" - integrity sha512-k0CIe3tXUKTRSoEx1LQEPFU9vRQfqHtl+kf8eNnDqb4AUJEy5pz6aIiog+YWtVm2jpggjS1laH68bPsR+KWWPQ== +"@babel/helper-hoist-variables@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz#d4d2c8fb4baeaa5c68b99cc8245c56554f926678" + integrity sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q== dependencies: - "@babel/types" "^7.12.1" + "@babel/types" "^7.18.6" -"@babel/helper-module-imports@^7.0.0", "@babel/helper-module-imports@^7.12.1", "@babel/helper-module-imports@^7.16.0", "@babel/helper-module-imports@^7.16.7": +"@babel/helper-module-imports@^7.0.0", "@babel/helper-module-imports@^7.16.0": version "7.16.7" resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz#25612a8091a999704461c8a222d0efec5d091437" integrity sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg== dependencies: "@babel/types" "^7.16.7" -"@babel/helper-module-transforms@^7.12.1", "@babel/helper-module-transforms@^7.18.0": - version "7.18.0" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.18.0.tgz#baf05dec7a5875fb9235bd34ca18bad4e21221cd" - integrity sha512-kclUYSUBIjlvnzN2++K9f2qzYKFgjmnmjwL4zlmU5f8ZtzgWe8s0rUPSTGy2HmK4P8T52MQsS+HTQAgZd3dMEA== - dependencies: - "@babel/helper-environment-visitor" "^7.16.7" - "@babel/helper-module-imports" "^7.16.7" - "@babel/helper-simple-access" "^7.17.7" - "@babel/helper-split-export-declaration" "^7.16.7" - "@babel/helper-validator-identifier" "^7.16.7" - "@babel/template" "^7.16.7" - "@babel/traverse" "^7.18.0" - "@babel/types" "^7.18.0" - -"@babel/helper-optimise-call-expression@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz#50dc96413d594f995a77905905b05893cd779673" - integrity sha512-n3UGKY4VXwXThEiKrgRAoVPBMqeoPgHVqiHZOanAJCG9nQUL2pLRQirUzl0ioKclHGpGqRgIOkgcIJaIWLpygg== - dependencies: - "@babel/types" "^7.10.4" - -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.17.12", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": - version "7.17.12" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.17.12.tgz#86c2347da5acbf5583ba0a10aed4c9bf9da9cf96" - integrity sha512-JDkf04mqtN3y4iAbO1hv9U2ARpPyPL1zqyWs/2WG1pgSq9llHFjStX5jdxb84himgJm+8Ng+x0oiWF/nw/XQKA== - -"@babel/helper-regex@^7.10.4": - version "7.10.5" - resolved "https://registry.yarnpkg.com/@babel/helper-regex/-/helper-regex-7.10.5.tgz#32dfbb79899073c415557053a19bd055aae50ae0" - integrity sha512-68kdUAzDrljqBrio7DYAEgCoJHxppJOERHOgOrDN7WjOzP0ZQ1LsSDRXcemzVZaLvjaJsJEESb6qt+znNuENDg== - dependencies: - lodash "^4.17.19" - -"@babel/helper-remap-async-to-generator@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.12.1.tgz#8c4dbbf916314f6047dc05e6a2217074238347fd" - integrity sha512-9d0KQCRM8clMPcDwo8SevNs+/9a8yWVVmaE80FGJcEP8N1qToREmWEGnBn8BUlJhYRFz6fqxeRL1sl5Ogsed7A== - dependencies: - "@babel/helper-annotate-as-pure" "^7.10.4" - "@babel/helper-wrap-function" "^7.10.4" - "@babel/types" "^7.12.1" - -"@babel/helper-replace-supers@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.12.1.tgz#f15c9cc897439281891e11d5ce12562ac0cf3fa9" - integrity sha512-zJjTvtNJnCFsCXVi5rUInstLd/EIVNmIKA1Q9ynESmMBWPWd+7sdR+G4/wdu+Mppfep0XLyG2m7EBPvjCeFyrw== - dependencies: - "@babel/helper-member-expression-to-functions" "^7.12.1" - "@babel/helper-optimise-call-expression" "^7.10.4" - "@babel/traverse" "^7.12.1" - "@babel/types" "^7.12.1" - -"@babel/helper-simple-access@^7.17.7", "@babel/helper-simple-access@^7.18.2": - version "7.18.2" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.18.2.tgz#4dc473c2169ac3a1c9f4a51cfcd091d1c36fcff9" - integrity sha512-7LIrjYzndorDY88MycupkpQLKS1AFfsVRm2k/9PtKScSy5tZq0McZTj+DiMRynboZfIqOKvo03pmhTaUgiD6fQ== - dependencies: - "@babel/types" "^7.18.2" - -"@babel/helper-skip-transparent-expression-wrappers@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.12.1.tgz#462dc63a7e435ade8468385c63d2b84cce4b3cbf" - integrity sha512-Mf5AUuhG1/OCChOJ/HcADmvcHM42WJockombn8ATJG3OnyiSxBK/Mm5x78BQWvmtXZKHgbjdGL2kin/HOLlZGA== - dependencies: - "@babel/types" "^7.12.1" - -"@babel/helper-split-export-declaration@^7.10.4", "@babel/helper-split-export-declaration@^7.16.7": +"@babel/helper-module-imports@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz#1e3ebdbbd08aad1437b428c50204db13c5a3ca6e" + integrity sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA== + dependencies: + "@babel/types" "^7.18.6" + +"@babel/helper-module-transforms@^7.19.6": + version "7.19.6" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.19.6.tgz#6c52cc3ac63b70952d33ee987cbee1c9368b533f" + integrity sha512-fCmcfQo/KYr/VXXDIyd3CBGZ6AFhPFy1TfSEJ+PilGVlQT6jcbqtHAM4C1EciRqMza7/TpOUZliuSH+U6HAhJw== + dependencies: + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-module-imports" "^7.18.6" + "@babel/helper-simple-access" "^7.19.4" + "@babel/helper-split-export-declaration" "^7.18.6" + "@babel/helper-validator-identifier" "^7.19.1" + "@babel/template" "^7.18.10" + "@babel/traverse" "^7.19.6" + "@babel/types" "^7.19.4" + +"@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz#4796bb14961521f0f8715990bee2fb6e51ce21bf" + integrity sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw== + +"@babel/helper-simple-access@^7.19.4": + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.19.4.tgz#be553f4951ac6352df2567f7daa19a0ee15668e7" + integrity sha512-f9Xq6WqBFqaDfbCzn2w85hwklswz5qsKlh7f08w4Y9yhJHpnNC0QemtSkK5YyOY8kPGvyiwdzZksGUhnGdaUIg== + dependencies: + "@babel/types" "^7.19.4" + +"@babel/helper-split-export-declaration@^7.16.7": version "7.16.7" resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz#0b648c0c42da9d3920d85ad585f2778620b8726b" integrity sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw== dependencies: "@babel/types" "^7.16.7" -"@babel/helper-validator-identifier@^7.10.4": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz#e8c602438c4a8195751243da9031d1607d247cad" - integrity sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw== +"@babel/helper-split-export-declaration@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz#7367949bc75b20c6d5a5d4a97bba2824ae8ef075" + integrity sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA== + dependencies: + "@babel/types" "^7.18.6" + +"@babel/helper-string-parser@^7.19.4": + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz#38d3acb654b4701a9b77fb0615a96f775c3a9e63" + integrity sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw== "@babel/helper-validator-identifier@^7.16.7", "@babel/helper-validator-identifier@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz#9c97e30d31b2b8c72a1d08984f2ca9b574d7a076" integrity sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g== -"@babel/helper-validator-option@^7.12.1", "@babel/helper-validator-option@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz#b203ce62ce5fe153899b617c08957de860de4d23" - integrity sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ== +"@babel/helper-validator-identifier@^7.19.1": + version "7.19.1" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2" + integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w== -"@babel/helper-wrap-function@^7.10.4": - version "7.12.3" - resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.12.3.tgz#3332339fc4d1fbbf1c27d7958c27d34708e990d9" - integrity sha512-Cvb8IuJDln3rs6tzjW3Y8UeelAOdnpB8xtQ4sme2MSZ9wOxrbThporC0y/EtE16VAtoyEfLM404Xr1e0OOp+ow== - dependencies: - "@babel/helper-function-name" "^7.10.4" - "@babel/template" "^7.10.4" - "@babel/traverse" "^7.10.4" - "@babel/types" "^7.10.4" +"@babel/helper-validator-option@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz#bf0d2b5a509b1f336099e4ff36e1a63aa5db4db8" + integrity sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw== -"@babel/helpers@^7.18.2": - version "7.18.2" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.18.2.tgz#970d74f0deadc3f5a938bfa250738eb4ac889384" - integrity sha512-j+d+u5xT5utcQSzrh9p+PaJX94h++KN+ng9b9WEJq7pkUPAd61FGqhjuUEdfknb3E/uDBb7ruwEeKkIxNJPIrg== +"@babel/helpers@^7.19.4": + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.19.4.tgz#42154945f87b8148df7203a25c31ba9a73be46c5" + integrity sha512-G+z3aOx2nfDHwX/kyVii5fJq+bgscg89/dJNWpYeKeBv3v9xX8EIabmx1k6u9LS04H7nROFVRVK+e3k0VHp+sw== dependencies: - "@babel/template" "^7.16.7" - "@babel/traverse" "^7.18.2" - "@babel/types" "^7.18.2" + "@babel/template" "^7.18.10" + "@babel/traverse" "^7.19.4" + "@babel/types" "^7.19.4" "@babel/highlight@^7.16.7", "@babel/highlight@^7.18.6": version "7.18.6" @@ -287,752 +224,433 @@ chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.12.5", "@babel/parser@^7.14.7", "@babel/parser@^7.16.7", "@babel/parser@^7.18.5": +"@babel/parser@^7.12.5", "@babel/parser@^7.16.7", "@babel/parser@^7.18.5": version "7.18.5" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.5.tgz#337062363436a893a2d22faa60be5bb37091c83c" integrity sha512-YZWVaglMiplo7v8f1oMQ5ZPQr0vn7HPeZXxXWsxXJRjGVrzUFn9OxFQl1sb5wzfootjA/yChhW84BV+383FSOw== -"@babel/plugin-proposal-async-generator-functions@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.12.1.tgz#dc6c1170e27d8aca99ff65f4925bd06b1c90550e" - integrity sha512-d+/o30tJxFxrA1lhzJqiUcEJdI6jKlNregCv5bASeGf2Q4MXmnwH7viDo7nhx1/ohf09oaH8j1GVYG/e3Yqk6A== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - "@babel/helper-remap-async-to-generator" "^7.12.1" - "@babel/plugin-syntax-async-generators" "^7.8.0" - -"@babel/plugin-proposal-class-properties@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.12.1.tgz#a082ff541f2a29a4821065b8add9346c0c16e5de" - integrity sha512-cKp3dlQsFsEs5CWKnN7BnSHOd0EOW8EKpEjkoz1pO2E5KzIDNV9Ros1b0CnmbVgAGXJubOYVBOGCT1OmJwOI7w== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.12.1" - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-proposal-dynamic-import@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.12.1.tgz#43eb5c2a3487ecd98c5c8ea8b5fdb69a2749b2dc" - integrity sha512-a4rhUSZFuq5W8/OO8H7BL5zspjnc1FLd9hlOxIK/f7qG4a0qsqk8uvF/ywgBA8/OmjsapjpvaEOYItfGG1qIvQ== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - "@babel/plugin-syntax-dynamic-import" "^7.8.0" - -"@babel/plugin-proposal-export-namespace-from@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.12.1.tgz#8b9b8f376b2d88f5dd774e4d24a5cc2e3679b6d4" - integrity sha512-6CThGf0irEkzujYS5LQcjBx8j/4aQGiVv7J9+2f7pGfxqyKh3WnmVJYW3hdrQjyksErMGBPQrCnHfOtna+WLbw== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - "@babel/plugin-syntax-export-namespace-from" "^7.8.3" - -"@babel/plugin-proposal-json-strings@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.12.1.tgz#d45423b517714eedd5621a9dfdc03fa9f4eb241c" - integrity sha512-GoLDUi6U9ZLzlSda2Df++VSqDJg3CG+dR0+iWsv6XRw1rEq+zwt4DirM9yrxW6XWaTpmai1cWJLMfM8qQJf+yw== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - "@babel/plugin-syntax-json-strings" "^7.8.0" - -"@babel/plugin-proposal-logical-assignment-operators@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.12.1.tgz#f2c490d36e1b3c9659241034a5d2cd50263a2751" - integrity sha512-k8ZmVv0JU+4gcUGeCDZOGd0lCIamU/sMtIiX3UWnUc5yzgq6YUGyEolNYD+MLYKfSzgECPcqetVcJP9Afe/aCA== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" - -"@babel/plugin-proposal-nullish-coalescing-operator@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.12.1.tgz#3ed4fff31c015e7f3f1467f190dbe545cd7b046c" - integrity sha512-nZY0ESiaQDI1y96+jk6VxMOaL4LPo/QDHBqL+SF3/vl6dHkTwHlOI8L4ZwuRBHgakRBw5zsVylel7QPbbGuYgg== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0" - -"@babel/plugin-proposal-numeric-separator@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.12.1.tgz#0e2c6774c4ce48be412119b4d693ac777f7685a6" - integrity sha512-MR7Ok+Af3OhNTCxYVjJZHS0t97ydnJZt/DbR4WISO39iDnhiD8XHrY12xuSJ90FFEGjir0Fzyyn7g/zY6hxbxA== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - "@babel/plugin-syntax-numeric-separator" "^7.10.4" - -"@babel/plugin-proposal-object-rest-spread@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.12.1.tgz#def9bd03cea0f9b72283dac0ec22d289c7691069" - integrity sha512-s6SowJIjzlhx8o7lsFx5zmY4At6CTtDvgNQDdPzkBQucle58A6b/TTeEBYtyDgmcXjUTM+vE8YOGHZzzbc/ioA== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - "@babel/plugin-syntax-object-rest-spread" "^7.8.0" - "@babel/plugin-transform-parameters" "^7.12.1" - -"@babel/plugin-proposal-optional-catch-binding@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.12.1.tgz#ccc2421af64d3aae50b558a71cede929a5ab2942" - integrity sha512-hFvIjgprh9mMw5v42sJWLI1lzU5L2sznP805zeT6rySVRA0Y18StRhDqhSxlap0oVgItRsB6WSROp4YnJTJz0g== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - "@babel/plugin-syntax-optional-catch-binding" "^7.8.0" - -"@babel/plugin-proposal-optional-chaining@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.12.1.tgz#cce122203fc8a32794296fc377c6dedaf4363797" - integrity sha512-c2uRpY6WzaVDzynVY9liyykS+kVU+WRZPMPYpkelXH8KBt1oXoI89kPbZKKG/jDT5UK92FTW2fZkZaJhdiBabw== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - "@babel/helper-skip-transparent-expression-wrappers" "^7.12.1" - "@babel/plugin-syntax-optional-chaining" "^7.8.0" - -"@babel/plugin-proposal-private-methods@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.12.1.tgz#86814f6e7a21374c980c10d38b4493e703f4a389" - integrity sha512-mwZ1phvH7/NHK6Kf8LP7MYDogGV+DKB1mryFOEwx5EBNQrosvIczzZFTUmWaeujd5xT6G1ELYWUz3CutMhjE1w== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.12.1" - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-proposal-unicode-property-regex@^7.12.1", "@babel/plugin-proposal-unicode-property-regex@^7.4.4": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.12.1.tgz#2a183958d417765b9eae334f47758e5d6a82e072" - integrity sha512-MYq+l+PvHuw/rKUz1at/vb6nCnQ2gmJBNaM62z0OgH7B2W1D9pvkpYtlti9bGtizNIU1K3zm4bZF9F91efVY0w== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.12.1" - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-syntax-async-generators@^7.8.0", "@babel/plugin-syntax-async-generators@^7.8.4": - version "7.8.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" - integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-bigint@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea" - integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-class-properties@^7.12.1", "@babel/plugin-syntax-class-properties@^7.8.3": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" - integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== - dependencies: - "@babel/helper-plugin-utils" "^7.12.13" - -"@babel/plugin-syntax-dynamic-import@^7.8.0", "@babel/plugin-syntax-dynamic-import@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz#62bf98b2da3cd21d626154fc96ee5b3cb68eacb3" - integrity sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-export-namespace-from@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz#028964a9ba80dbc094c915c487ad7c4e7a66465a" - integrity sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q== - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - -"@babel/plugin-syntax-import-meta@^7.8.3": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" - integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-syntax-json-strings@^7.8.0", "@babel/plugin-syntax-json-strings@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" - integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-jsx@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.12.1.tgz#9d9d357cc818aa7ae7935917c1257f67677a0926" - integrity sha512-1yRi7yAtB0ETgxdY9ti/p2TivUxJkTdhu/ZbF9MshVGqOx1TdB3b7xCXs49Fupgg50N45KcAsRP/ZqWjs9SRjg== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-syntax-logical-assignment-operators@^7.10.4", "@babel/plugin-syntax-logical-assignment-operators@^7.8.3": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" - integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.0", "@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" - integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-numeric-separator@^7.10.4", "@babel/plugin-syntax-numeric-separator@^7.8.3": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" - integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" +"@babel/parser@^7.18.10", "@babel/parser@^7.19.6": + version "7.19.6" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.19.6.tgz#b923430cb94f58a7eae8facbffa9efd19130e7f8" + integrity sha512-h1IUp81s2JYJ3mRkdxJgs4UvmSsRvDrx5ICSJbPvtWYv5i1nTBGcBpnog+89rAFMwvvru6E5NUHdBe01UeSzYA== -"@babel/plugin-syntax-object-rest-spread@^7.8.0", "@babel/plugin-syntax-object-rest-spread@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" - integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== +"@babel/plugin-syntax-jsx@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz#a8feef63b010150abd97f1649ec296e849943ca0" + integrity sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q== dependencies: - "@babel/helper-plugin-utils" "^7.8.0" + "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-syntax-optional-catch-binding@^7.8.0", "@babel/plugin-syntax-optional-catch-binding@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" - integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== +"@babel/plugin-transform-react-jsx-development@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.18.6.tgz#dbe5c972811e49c7405b630e4d0d2e1380c0ddc5" + integrity sha512-SA6HEjwYFKF7WDjWcMcMGUimmw/nhNRDWxr+KaLSCrkD/LMDBvWRmHAYgE1HDeF8KUuI8OAu+RT6EOtKxSW2qA== dependencies: - "@babel/helper-plugin-utils" "^7.8.0" + "@babel/plugin-transform-react-jsx" "^7.18.6" -"@babel/plugin-syntax-optional-chaining@^7.8.0", "@babel/plugin-syntax-optional-chaining@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" +"@babel/plugin-transform-react-jsx-self@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.18.6.tgz#3849401bab7ae8ffa1e3e5687c94a753fc75bda7" + integrity sha512-A0LQGx4+4Jv7u/tWzoJF7alZwnBDQd6cGLh9P+Ttk4dpiL+J5p7NSNv/9tlEFFJDq3kjxOavWmbm6t0Gk+A3Ig== dependencies: - "@babel/helper-plugin-utils" "^7.8.0" + "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-syntax-top-level-await@^7.12.1", "@babel/plugin-syntax-top-level-await@^7.8.3": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" - integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== +"@babel/plugin-transform-react-jsx-source@^7.19.6": + version "7.19.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.19.6.tgz#88578ae8331e5887e8ce28e4c9dc83fb29da0b86" + integrity sha512-RpAi004QyMNisst/pvSanoRdJ4q+jMCWyk9zdw/CyLB9j8RXEahodR6l2GyttDRyEVWZtbN+TpLiHJ3t34LbsQ== dependencies: - "@babel/helper-plugin-utils" "^7.14.5" + "@babel/helper-plugin-utils" "^7.19.0" -"@babel/plugin-syntax-typescript@^7.12.1", "@babel/plugin-syntax-typescript@^7.7.2": - version "7.17.12" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.17.12.tgz#b54fc3be6de734a56b87508f99d6428b5b605a7b" - integrity sha512-TYY0SXFiO31YXtNg3HtFwNJHjLsAyIIhAhNWkQ5whPPS7HWUFlg9z0Ta4qAQNjQbP1wsSt/oKkmZ/4/WWdMUpw== +"@babel/plugin-transform-react-jsx@^7.18.6", "@babel/plugin-transform-react-jsx@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.19.0.tgz#b3cbb7c3a00b92ec8ae1027910e331ba5c500eb9" + integrity sha512-UVEvX3tXie3Szm3emi1+G63jyw1w5IcMY0FSKM+CRnKRI5Mr1YbCNgsSTwoTwKphQEG9P+QqmuRFneJPZuHNhg== dependencies: - "@babel/helper-plugin-utils" "^7.17.12" + "@babel/helper-annotate-as-pure" "^7.18.6" + "@babel/helper-module-imports" "^7.18.6" + "@babel/helper-plugin-utils" "^7.19.0" + "@babel/plugin-syntax-jsx" "^7.18.6" + "@babel/types" "^7.19.0" -"@babel/plugin-transform-arrow-functions@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.12.1.tgz#8083ffc86ac8e777fbe24b5967c4b2521f3cb2b3" - integrity sha512-5QB50qyN44fzzz4/qxDPQMBCTHgxg3n0xRBLJUmBlLoU/sFvxVWGZF/ZUfMVDQuJUKXaBhbupxIzIfZ6Fwk/0A== +"@babel/runtime@^7.12.5", "@babel/runtime@^7.17.2", "@babel/runtime@^7.7.2", "@babel/runtime@^7.9.2": + version "7.18.3" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.18.3.tgz#c7b654b57f6f63cf7f8b418ac9ca04408c4579f4" + integrity sha512-38Y8f7YUhce/K7RMwTp7m0uCumpv9hZkitCbBClqQIow1qSbCvGkcegKOXpEWCQLfWmevgRiWokZ1GkpfhbZug== dependencies: - "@babel/helper-plugin-utils" "^7.10.4" + regenerator-runtime "^0.13.4" -"@babel/plugin-transform-async-to-generator@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.12.1.tgz#3849a49cc2a22e9743cbd6b52926d30337229af1" - integrity sha512-SDtqoEcarK1DFlRJ1hHRY5HvJUj5kX4qmtpMAm2QnhOlyuMC4TMdCRgW6WXpv93rZeYNeLP22y8Aq2dbcDRM1A== +"@babel/runtime@^7.14.5", "@babel/runtime@^7.15.4": + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.19.4.tgz#a42f814502ee467d55b38dd1c256f53a7b885c78" + integrity sha512-EXpLCrk55f+cYqmHsSR+yD/0gAIMxxA9QK9lnQWzhMCvt+YmoBN7Zx94s++Kv0+unHk39vxNO8t+CMA2WSS3wA== dependencies: - "@babel/helper-module-imports" "^7.12.1" - "@babel/helper-plugin-utils" "^7.10.4" - "@babel/helper-remap-async-to-generator" "^7.12.1" + regenerator-runtime "^0.13.4" -"@babel/plugin-transform-block-scoped-functions@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.12.1.tgz#f2a1a365bde2b7112e0a6ded9067fdd7c07905d9" - integrity sha512-5OpxfuYnSgPalRpo8EWGPzIYf0lHBWORCkj5M0oLBwHdlux9Ri36QqGW3/LR13RSVOAoUUMzoPI/jpE4ABcHoA== +"@babel/template@^7.12.13", "@babel/template@^7.18.10": + version "7.18.10" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.18.10.tgz#6f9134835970d1dbf0835c0d100c9f38de0c5e71" + integrity sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA== dependencies: - "@babel/helper-plugin-utils" "^7.10.4" + "@babel/code-frame" "^7.18.6" + "@babel/parser" "^7.18.10" + "@babel/types" "^7.18.10" -"@babel/plugin-transform-block-scoping@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.12.1.tgz#f0ee727874b42a208a48a586b84c3d222c2bbef1" - integrity sha512-zJyAC9sZdE60r1nVQHblcfCj29Dh2Y0DOvlMkcqSo0ckqjiCwNiUezUKw+RjOCwGfpLRwnAeQ2XlLpsnGkvv9w== +"@babel/template@^7.14.5", "@babel/template@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.16.7.tgz#8d126c8701fde4d66b264b3eba3d96f07666d155" + integrity sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w== dependencies: - "@babel/helper-plugin-utils" "^7.10.4" + "@babel/code-frame" "^7.16.7" + "@babel/parser" "^7.16.7" + "@babel/types" "^7.16.7" -"@babel/plugin-transform-classes@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.12.1.tgz#65e650fcaddd3d88ddce67c0f834a3d436a32db6" - integrity sha512-/74xkA7bVdzQTBeSUhLLJgYIcxw/dpEpCdRDiHgPJ3Mv6uC11UhjpOhl72CgqbBCmt1qtssCyB2xnJm1+PFjog== - dependencies: - "@babel/helper-annotate-as-pure" "^7.10.4" - "@babel/helper-define-map" "^7.10.4" - "@babel/helper-function-name" "^7.10.4" - "@babel/helper-optimise-call-expression" "^7.10.4" - "@babel/helper-plugin-utils" "^7.10.4" - "@babel/helper-replace-supers" "^7.12.1" - "@babel/helper-split-export-declaration" "^7.10.4" +"@babel/traverse@^7.19.4", "@babel/traverse@^7.19.6": + version "7.19.6" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.19.6.tgz#7b4c865611df6d99cb131eec2e8ac71656a490dc" + integrity sha512-6l5HrUCzFM04mfbG09AagtYyR2P0B71B1wN7PfSPiksDPz2k5H9CBC1tcZpz2M8OxbKTPccByoOJ22rUKbpmQQ== + dependencies: + "@babel/code-frame" "^7.18.6" + "@babel/generator" "^7.19.6" + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-function-name" "^7.19.0" + "@babel/helper-hoist-variables" "^7.18.6" + "@babel/helper-split-export-declaration" "^7.18.6" + "@babel/parser" "^7.19.6" + "@babel/types" "^7.19.4" + debug "^4.1.0" globals "^11.1.0" -"@babel/plugin-transform-computed-properties@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.12.1.tgz#d68cf6c9b7f838a8a4144badbe97541ea0904852" - integrity sha512-vVUOYpPWB7BkgUWPo4C44mUQHpTZXakEqFjbv8rQMg7TC6S6ZhGZ3otQcRH6u7+adSlE5i0sp63eMC/XGffrzg== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-transform-destructuring@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.12.1.tgz#b9a570fe0d0a8d460116413cb4f97e8e08b2f847" - integrity sha512-fRMYFKuzi/rSiYb2uRLiUENJOKq4Gnl+6qOv5f8z0TZXg3llUwUhsNNwrwaT/6dUhJTzNpBr+CUvEWBtfNY1cw== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-transform-dotall-regex@^7.12.1", "@babel/plugin-transform-dotall-regex@^7.4.4": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.12.1.tgz#a1d16c14862817b6409c0a678d6f9373ca9cd975" - integrity sha512-B2pXeRKoLszfEW7J4Hg9LoFaWEbr/kzo3teWHmtFCszjRNa/b40f9mfeqZsIDLLt/FjwQ6pz/Gdlwy85xNckBA== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.12.1" - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-transform-duplicate-keys@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.12.1.tgz#745661baba295ac06e686822797a69fbaa2ca228" - integrity sha512-iRght0T0HztAb/CazveUpUQrZY+aGKKaWXMJ4uf9YJtqxSUe09j3wteztCUDRHs+SRAL7yMuFqUsLoAKKzgXjw== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-transform-exponentiation-operator@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.12.1.tgz#b0f2ed356ba1be1428ecaf128ff8a24f02830ae0" - integrity sha512-7tqwy2bv48q+c1EHbXK0Zx3KXd2RVQp6OC7PbwFNt/dPTAV3Lu5sWtWuAj8owr5wqtWnqHfl2/mJlUmqkChKug== - dependencies: - "@babel/helper-builder-binary-assignment-operator-visitor" "^7.10.4" - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-transform-for-of@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.12.1.tgz#07640f28867ed16f9511c99c888291f560921cfa" - integrity sha512-Zaeq10naAsuHo7heQvyV0ptj4dlZJwZgNAtBYBnu5nNKJoW62m0zKcIEyVECrUKErkUkg6ajMy4ZfnVZciSBhg== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-transform-function-name@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.12.1.tgz#2ec76258c70fe08c6d7da154003a480620eba667" - integrity sha512-JF3UgJUILoFrFMEnOJLJkRHSk6LUSXLmEFsA23aR2O5CSLUxbeUX1IZ1YQ7Sn0aXb601Ncwjx73a+FVqgcljVw== - dependencies: - "@babel/helper-function-name" "^7.10.4" - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-transform-literals@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.12.1.tgz#d73b803a26b37017ddf9d3bb8f4dc58bfb806f57" - integrity sha512-+PxVGA+2Ag6uGgL0A5f+9rklOnnMccwEBzwYFL3EUaKuiyVnUipyXncFcfjSkbimLrODoqki1U9XxZzTvfN7IQ== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-transform-member-expression-literals@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.12.1.tgz#496038602daf1514a64d43d8e17cbb2755e0c3ad" - integrity sha512-1sxePl6z9ad0gFMB9KqmYofk34flq62aqMt9NqliS/7hPEpURUCMbyHXrMPlo282iY7nAvUB1aQd5mg79UD9Jg== +"@babel/traverse@^7.4.5": + version "7.18.5" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.18.5.tgz#94a8195ad9642801837988ab77f36e992d9a20cd" + integrity sha512-aKXj1KT66sBj0vVzk6rEeAO6Z9aiiQ68wfDgge3nHhA/my6xMM/7HGQUNumKZaoa2qUPQ5whJG9aAifsxUKfLA== dependencies: - "@babel/helper-plugin-utils" "^7.10.4" + "@babel/code-frame" "^7.16.7" + "@babel/generator" "^7.18.2" + "@babel/helper-environment-visitor" "^7.18.2" + "@babel/helper-function-name" "^7.17.9" + "@babel/helper-hoist-variables" "^7.16.7" + "@babel/helper-split-export-declaration" "^7.16.7" + "@babel/parser" "^7.18.5" + "@babel/types" "^7.18.4" + debug "^4.1.0" + globals "^11.1.0" -"@babel/plugin-transform-modules-amd@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.12.1.tgz#3154300b026185666eebb0c0ed7f8415fefcf6f9" - integrity sha512-tDW8hMkzad5oDtzsB70HIQQRBiTKrhfgwC/KkJeGsaNFTdWhKNt/BiE8c5yj19XiGyrxpbkOfH87qkNg1YGlOQ== +"@babel/types@^7.16.7", "@babel/types@^7.17.0", "@babel/types@^7.18.2", "@babel/types@^7.18.4": + version "7.18.4" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.18.4.tgz#27eae9b9fd18e9dccc3f9d6ad051336f307be354" + integrity sha512-ThN1mBcMq5pG/Vm2IcBmPPfyPXbd8S02rS+OBIDENdufvqC7Z/jHPCv9IcP01277aKtDI8g/2XysBN4hA8niiw== dependencies: - "@babel/helper-module-transforms" "^7.12.1" - "@babel/helper-plugin-utils" "^7.10.4" - babel-plugin-dynamic-import-node "^2.3.3" + "@babel/helper-validator-identifier" "^7.16.7" + to-fast-properties "^2.0.0" -"@babel/plugin-transform-modules-commonjs@^7.12.1", "@babel/plugin-transform-modules-commonjs@^7.18.2": - version "7.18.2" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.18.2.tgz#1aa8efa2e2a6e818b6a7f2235fceaf09bdb31e9e" - integrity sha512-f5A865gFPAJAEE0K7F/+nm5CmAE3y8AWlMBG9unu5j9+tk50UQVK0QS8RNxSp7MJf0wh97uYyLWt3Zvu71zyOQ== +"@babel/types@^7.18.10", "@babel/types@^7.18.6", "@babel/types@^7.19.0", "@babel/types@^7.19.4": + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.19.4.tgz#0dd5c91c573a202d600490a35b33246fed8a41c7" + integrity sha512-M5LK7nAeS6+9j7hAq+b3fQs+pNfUtTGq+yFFfHnauFA8zQtLRfmuipmsKDKKLuyG+wC8ABW43A153YNawNTEtw== dependencies: - "@babel/helper-module-transforms" "^7.18.0" - "@babel/helper-plugin-utils" "^7.17.12" - "@babel/helper-simple-access" "^7.18.2" - babel-plugin-dynamic-import-node "^2.3.3" + "@babel/helper-string-parser" "^7.19.4" + "@babel/helper-validator-identifier" "^7.19.1" + to-fast-properties "^2.0.0" -"@babel/plugin-transform-modules-systemjs@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.12.1.tgz#663fea620d593c93f214a464cd399bf6dc683086" - integrity sha512-Hn7cVvOavVh8yvW6fLwveFqSnd7rbQN3zJvoPNyNaQSvgfKmDBO9U1YL9+PCXGRlZD9tNdWTy5ACKqMuzyn32Q== +"@codemirror/autocomplete@^6.0.0": + version "6.3.0" + resolved "https://registry.yarnpkg.com/@codemirror/autocomplete/-/autocomplete-6.3.0.tgz#217e16bb6ce63374ec7b9d2a01d007ba53ff0aff" + integrity sha512-4jEvh3AjJZTDKazd10J6ZsCIqaYxDMCeua5ouQxY8hlFIml+nr7le0SgBhT3SIytFBmdzPK3AUhXGuW3T79nVg== dependencies: - "@babel/helper-hoist-variables" "^7.10.4" - "@babel/helper-module-transforms" "^7.12.1" - "@babel/helper-plugin-utils" "^7.10.4" - "@babel/helper-validator-identifier" "^7.10.4" - babel-plugin-dynamic-import-node "^2.3.3" + "@codemirror/language" "^6.0.0" + "@codemirror/state" "^6.0.0" + "@codemirror/view" "^6.0.0" + "@lezer/common" "^1.0.0" -"@babel/plugin-transform-modules-umd@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.12.1.tgz#eb5a218d6b1c68f3d6217b8fa2cc82fec6547902" - integrity sha512-aEIubCS0KHKM0zUos5fIoQm+AZUMt1ZvMpqz0/H5qAQ7vWylr9+PLYurT+Ic7ID/bKLd4q8hDovaG3Zch2uz5Q== +"@codemirror/commands@^6.0.0": + version "6.1.2" + resolved "https://registry.yarnpkg.com/@codemirror/commands/-/commands-6.1.2.tgz#84fb7d170047c3aeb7b0047ace59510bb19208de" + integrity sha512-sO3jdX1s0pam6lIdeSJLMN3DQ6mPEbM4yLvyKkdqtmd/UDwhXA5+AwFJ89rRXm6vTeOXBsE5cAmlos/t7MJdgg== dependencies: - "@babel/helper-module-transforms" "^7.12.1" - "@babel/helper-plugin-utils" "^7.10.4" + "@codemirror/language" "^6.0.0" + "@codemirror/state" "^6.0.0" + "@codemirror/view" "^6.0.0" + "@lezer/common" "^1.0.0" -"@babel/plugin-transform-named-capturing-groups-regex@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.12.1.tgz#b407f5c96be0d9f5f88467497fa82b30ac3e8753" - integrity sha512-tB43uQ62RHcoDp9v2Nsf+dSM8sbNodbEicbQNA53zHz8pWUhsgHSJCGpt7daXxRydjb0KnfmB+ChXOv3oADp1Q== +"@codemirror/lang-cpp@^6.0.0": + version "6.0.2" + resolved "https://registry.yarnpkg.com/@codemirror/lang-cpp/-/lang-cpp-6.0.2.tgz#076c98340c3beabde016d7d83e08eebe17254ef9" + integrity sha512-6oYEYUKHvrnacXxWxYa6t4puTlbN3dgV662BDfSH8+MfjQjVmP697/KYTDOqpxgerkvoNm7q5wlFMBeX8ZMocg== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.12.1" + "@codemirror/language" "^6.0.0" + "@lezer/cpp" "^1.0.0" -"@babel/plugin-transform-new-target@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.12.1.tgz#80073f02ee1bb2d365c3416490e085c95759dec0" - integrity sha512-+eW/VLcUL5L9IvJH7rT1sT0CzkdUTvPrXC2PXTn/7z7tXLBuKvezYbGdxD5WMRoyvyaujOq2fWoKl869heKjhw== +"@codemirror/lang-css@^6.0.0": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@codemirror/lang-css/-/lang-css-6.0.1.tgz#470fff614e4cfbbe796ec43103420d59c797dd7a" + integrity sha512-rlLq1Dt0WJl+2epLQeAsfqIsx3lGu4HStHCJu95nGGuz2P2fNugbU3dQYafr2VRjM4eMC9HviI6jvS98CNtG5w== + dependencies: + "@codemirror/autocomplete" "^6.0.0" + "@codemirror/language" "^6.0.0" + "@codemirror/state" "^6.0.0" + "@lezer/css" "^1.0.0" + +"@codemirror/lang-html@^6.0.0": + version "6.1.3" + resolved "https://registry.yarnpkg.com/@codemirror/lang-html/-/lang-html-6.1.3.tgz#b13f542e50c210f8f8ada2fb08f12836593e07fe" + integrity sha512-LmtIElopGK6bBfddAyjBitS6hz8nFr/PVUtvqmfomXlHB4m+Op2d5eGk/X9/CSby6Y8NqXXkGa3yDd9lfJ6Qlg== + dependencies: + "@codemirror/autocomplete" "^6.0.0" + "@codemirror/lang-css" "^6.0.0" + "@codemirror/lang-javascript" "^6.0.0" + "@codemirror/language" "^6.0.0" + "@codemirror/state" "^6.0.0" + "@codemirror/view" "^6.2.2" + "@lezer/common" "^1.0.0" + "@lezer/html" "^1.0.1" + +"@codemirror/lang-java@^6.0.0": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@codemirror/lang-java/-/lang-java-6.0.1.tgz#03bd06334da7c8feb9dff6db01ac6d85bd2e48bb" + integrity sha512-OOnmhH67h97jHzCuFaIEspbmsT98fNdhVhmA3zCxW0cn7l8rChDhZtwiwJ/JOKXgfm4J+ELxQihxaI7bj7mJRg== dependencies: - "@babel/helper-plugin-utils" "^7.10.4" + "@codemirror/language" "^6.0.0" + "@lezer/java" "^1.0.0" -"@babel/plugin-transform-object-super@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.12.1.tgz#4ea08696b8d2e65841d0c7706482b048bed1066e" - integrity sha512-AvypiGJH9hsquNUn+RXVcBdeE3KHPZexWRdimhuV59cSoOt5kFBmqlByorAeUlGG2CJWd0U+4ZtNKga/TB0cAw== +"@codemirror/lang-javascript@^6.0.0": + version "6.1.1" + resolved "https://registry.yarnpkg.com/@codemirror/lang-javascript/-/lang-javascript-6.1.1.tgz#f920192db30531927a02b8a1af9cf3c3d895101c" + integrity sha512-F4+kiuC5d5dUSJmff96tJQwpEXs/tX/4bapMRnZWW6bHKK1Fx6MunTzopkCUWRa9bF87GPmb9m7Qtg7Yv8f3uQ== + dependencies: + "@codemirror/autocomplete" "^6.0.0" + "@codemirror/language" "^6.0.0" + "@codemirror/lint" "^6.0.0" + "@codemirror/state" "^6.0.0" + "@codemirror/view" "^6.0.0" + "@lezer/common" "^1.0.0" + "@lezer/javascript" "^1.0.0" + +"@codemirror/lang-json@^6.0.0": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@codemirror/lang-json/-/lang-json-6.0.1.tgz#0a0be701a5619c4b0f8991f9b5e95fe33f462330" + integrity sha512-+T1flHdgpqDDlJZ2Lkil/rLiRy684WMLc74xUnjJH48GQdfJo/pudlTRreZmKwzP8/tGdKf83wlbAdOCzlJOGQ== dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - "@babel/helper-replace-supers" "^7.12.1" + "@codemirror/language" "^6.0.0" + "@lezer/json" "^1.0.0" -"@babel/plugin-transform-parameters@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.12.1.tgz#d2e963b038771650c922eff593799c96d853255d" - integrity sha512-xq9C5EQhdPK23ZeCdMxl8bbRnAgHFrw5EOC3KJUsSylZqdkCaFEXxGSBuTSObOpiiHHNyb82es8M1QYgfQGfNg== +"@codemirror/lang-lezer@^6.0.0": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@codemirror/lang-lezer/-/lang-lezer-6.0.1.tgz#16a5909ab8ab4a23e9b214476413dc92a3191780" + integrity sha512-WHwjI7OqKFBEfkunohweqA5B/jIlxaZso6Nl3weVckz8EafYbPZldQEKSDb4QQ9H9BUkle4PVELP4sftKoA0uQ== dependencies: - "@babel/helper-plugin-utils" "^7.10.4" + "@codemirror/language" "^6.0.0" + "@codemirror/state" "^6.0.0" + "@lezer/common" "^1.0.0" + "@lezer/lezer" "^1.0.0" -"@babel/plugin-transform-property-literals@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.12.1.tgz#41bc81200d730abb4456ab8b3fbd5537b59adecd" - integrity sha512-6MTCR/mZ1MQS+AwZLplX4cEySjCpnIF26ToWo942nqn8hXSm7McaHQNeGx/pt7suI1TWOWMfa/NgBhiqSnX0cQ== +"@codemirror/lang-markdown@^6.0.0": + version "6.0.4" + resolved "https://registry.yarnpkg.com/@codemirror/lang-markdown/-/lang-markdown-6.0.4.tgz#184eebadb232a5fb60cfcb66163da838ba646983" + integrity sha512-w50etMCYnm4btsVwOkREVc73sHk2+ZXA0q0nb7hNhjQ/NeEix9jRa63l/FUgrsfG2jjuRqsXTNjGdmmcorkTBQ== dependencies: - "@babel/helper-plugin-utils" "^7.10.4" + "@codemirror/lang-html" "^6.0.0" + "@codemirror/language" "^6.3.0" + "@codemirror/state" "^6.0.0" + "@codemirror/view" "^6.0.0" + "@lezer/common" "^1.0.0" + "@lezer/markdown" "^1.0.0" -"@babel/plugin-transform-react-display-name@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.12.1.tgz#1cbcd0c3b1d6648c55374a22fc9b6b7e5341c00d" - integrity sha512-cAzB+UzBIrekfYxyLlFqf/OagTvHLcVBb5vpouzkYkBclRPraiygVnafvAoipErZLI8ANv8Ecn6E/m5qPXD26w== +"@codemirror/lang-php@^6.0.0": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@codemirror/lang-php/-/lang-php-6.0.1.tgz#fa34cc75562178325861a5731f79bd621f57ffaa" + integrity sha512-ublojMdw/PNWa7qdN5TMsjmqkNuTBD3k6ndZ4Z0S25SBAiweFGyY68AS3xNcIOlb6DDFDvKlinLQ40vSLqf8xA== dependencies: - "@babel/helper-plugin-utils" "^7.10.4" + "@codemirror/lang-html" "^6.0.0" + "@codemirror/language" "^6.0.0" + "@codemirror/state" "^6.0.0" + "@lezer/common" "^1.0.0" + "@lezer/php" "^1.0.0" -"@babel/plugin-transform-react-jsx-development@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.12.1.tgz#0b8f8cd531dcf7991f1e5f2c10a2a4f1cfc78e36" - integrity sha512-IilcGWdN1yNgEGOrB96jbTplRh+V2Pz1EoEwsKsHfX1a/L40cUYuD71Zepa7C+ujv7kJIxnDftWeZbKNEqZjCQ== +"@codemirror/lang-python@^6.0.0": + version "6.0.4" + resolved "https://registry.yarnpkg.com/@codemirror/lang-python/-/lang-python-6.0.4.tgz#81dc262e57cce6cba72302420b22a63c2d2a5892" + integrity sha512-CuC7V6MVw4HshQuFaB1SMXHOSbKLnBnBXMzm9Zjb+uvkggyY8fXp79T9eYFzMn7fuadoPJcXyTcT/q/SRT7lvQ== dependencies: - "@babel/helper-builder-react-jsx-experimental" "^7.12.1" - "@babel/helper-plugin-utils" "^7.10.4" - "@babel/plugin-syntax-jsx" "^7.12.1" + "@codemirror/language" "^6.0.0" + "@lezer/python" "^1.0.0" -"@babel/plugin-transform-react-jsx-self@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.12.1.tgz#ef43cbca2a14f1bd17807dbe4376ff89d714cf28" - integrity sha512-FbpL0ieNWiiBB5tCldX17EtXgmzeEZjFrix72rQYeq9X6nUK38HCaxexzVQrZWXanxKJPKVVIU37gFjEQYkPkA== +"@codemirror/lang-rust@^6.0.0": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@codemirror/lang-rust/-/lang-rust-6.0.1.tgz#d6829fc7baa39a15bcd174a41a9e0a1bf7cf6ba8" + integrity sha512-344EMWFBzWArHWdZn/NcgkwMvZIWUR1GEBdwG8FEp++6o6vT6KL9V7vGs2ONsKxxFUPXKI0SPcWhyYyl2zPYxQ== dependencies: - "@babel/helper-plugin-utils" "^7.10.4" + "@codemirror/language" "^6.0.0" + "@lezer/rust" "^1.0.0" -"@babel/plugin-transform-react-jsx-source@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.12.1.tgz#d07de6863f468da0809edcf79a1aa8ce2a82a26b" - integrity sha512-keQ5kBfjJNRc6zZN1/nVHCd6LLIHq4aUKcVnvE/2l+ZZROSbqoiGFRtT5t3Is89XJxBQaP7NLZX2jgGHdZvvFQ== +"@codemirror/lang-sql@^6.0.0": + version "6.3.2" + resolved "https://registry.yarnpkg.com/@codemirror/lang-sql/-/lang-sql-6.3.2.tgz#478bbe741daa5b2f53b647ec6d1b5ca19f1ed297" + integrity sha512-lbk2jBVvVK6NkIEn6HU3RwLh368qEcGP5bknwv6kiLGffFZHNoXj/J/F/YNXSynsgswapBofb3J6yVwsjXYQPw== dependencies: - "@babel/helper-plugin-utils" "^7.10.4" + "@codemirror/autocomplete" "^6.0.0" + "@codemirror/language" "^6.0.0" + "@codemirror/state" "^6.0.0" + "@lezer/highlight" "^1.0.0" + "@lezer/lr" "^1.0.0" -"@babel/plugin-transform-react-jsx@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.12.1.tgz#c2d96c77c2b0e4362cc4e77a43ce7c2539d478cb" - integrity sha512-RmKejwnT0T0QzQUzcbP5p1VWlpnP8QHtdhEtLG55ZDQnJNalbF3eeDyu3dnGKvGzFIQiBzFhBYTwvv435p9Xpw== +"@codemirror/lang-wast@^6.0.0": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@codemirror/lang-wast/-/lang-wast-6.0.1.tgz#c15bec84548a5e9b0a43fa69fb63631d087d6047" + integrity sha512-sQLsqhRjl2MWG3rxZysX+2XAyed48KhLBHLgq9xcKxIJu3npH/G+BIXW5NM5mHeDUjG0jcGh9BcjP0NfMStuzA== dependencies: - "@babel/helper-builder-react-jsx" "^7.10.4" - "@babel/helper-builder-react-jsx-experimental" "^7.12.1" - "@babel/helper-plugin-utils" "^7.10.4" - "@babel/plugin-syntax-jsx" "^7.12.1" + "@codemirror/language" "^6.0.0" + "@lezer/highlight" "^1.0.0" + "@lezer/lr" "^1.0.0" -"@babel/plugin-transform-react-pure-annotations@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.12.1.tgz#05d46f0ab4d1339ac59adf20a1462c91b37a1a42" - integrity sha512-RqeaHiwZtphSIUZ5I85PEH19LOSzxfuEazoY7/pWASCAIBuATQzpSVD+eT6MebeeZT2F4eSL0u4vw6n4Nm0Mjg== - dependencies: - "@babel/helper-annotate-as-pure" "^7.10.4" - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-transform-regenerator@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.12.1.tgz#5f0a28d842f6462281f06a964e88ba8d7ab49753" - integrity sha512-gYrHqs5itw6i4PflFX3OdBPMQdPbF4bj2REIUxlMRUFk0/ZOAIpDFuViuxPjUL7YC8UPnf+XG7/utJvqXdPKng== - dependencies: - regenerator-transform "^0.14.2" - -"@babel/plugin-transform-reserved-words@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.12.1.tgz#6fdfc8cc7edcc42b36a7c12188c6787c873adcd8" - integrity sha512-pOnUfhyPKvZpVyBHhSBoX8vfA09b7r00Pmm1sH+29ae2hMTKVmSp4Ztsr8KBKjLjx17H0eJqaRC3bR2iThM54A== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-transform-runtime@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.12.1.tgz#04b792057eb460389ff6a4198e377614ea1e7ba5" - integrity sha512-Ac/H6G9FEIkS2tXsZjL4RAdS3L3WHxci0usAnz7laPWUmFiGtj7tIASChqKZMHTSQTQY6xDbOq+V1/vIq3QrWg== - dependencies: - "@babel/helper-module-imports" "^7.12.1" - "@babel/helper-plugin-utils" "^7.10.4" - resolve "^1.8.1" - semver "^5.5.1" - -"@babel/plugin-transform-shorthand-properties@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.12.1.tgz#0bf9cac5550fce0cfdf043420f661d645fdc75e3" - integrity sha512-GFZS3c/MhX1OusqB1MZ1ct2xRzX5ppQh2JU1h2Pnfk88HtFTM+TWQqJNfwkmxtPQtb/s1tk87oENfXJlx7rSDw== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-transform-spread@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.12.1.tgz#527f9f311be4ec7fdc2b79bb89f7bf884b3e1e1e" - integrity sha512-vuLp8CP0BE18zVYjsEBZ5xoCecMK6LBMMxYzJnh01rxQRvhNhH1csMMmBfNo5tGpGO+NhdSNW2mzIvBu3K1fng== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - "@babel/helper-skip-transparent-expression-wrappers" "^7.12.1" - -"@babel/plugin-transform-sticky-regex@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.12.1.tgz#5c24cf50de396d30e99afc8d1c700e8bce0f5caf" - integrity sha512-CiUgKQ3AGVk7kveIaPEET1jNDhZZEl1RPMWdTBE1799bdz++SwqDHStmxfCtDfBhQgCl38YRiSnrMuUMZIWSUQ== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - "@babel/helper-regex" "^7.10.4" - -"@babel/plugin-transform-template-literals@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.12.1.tgz#b43ece6ed9a79c0c71119f576d299ef09d942843" - integrity sha512-b4Zx3KHi+taXB1dVRBhVJtEPi9h1THCeKmae2qP0YdUHIFhVjtpqqNfxeVAa1xeHVhAy4SbHxEwx5cltAu5apw== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-transform-typeof-symbol@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.12.1.tgz#9ca6be343d42512fbc2e68236a82ae64bc7af78a" - integrity sha512-EPGgpGy+O5Kg5pJFNDKuxt9RdmTgj5sgrus2XVeMp/ZIbOESadgILUbm50SNpghOh3/6yrbsH+NB5+WJTmsA7Q== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-transform-typescript@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.12.1.tgz#d92cc0af504d510e26a754a7dbc2e5c8cd9c7ab4" - integrity sha512-VrsBByqAIntM+EYMqSm59SiMEf7qkmI9dqMt6RbD/wlwueWmYcI0FFK5Fj47pP6DRZm+3teXjosKlwcZJ5lIMw== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.12.1" - "@babel/helper-plugin-utils" "^7.10.4" - "@babel/plugin-syntax-typescript" "^7.12.1" - -"@babel/plugin-transform-unicode-escapes@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.12.1.tgz#5232b9f81ccb07070b7c3c36c67a1b78f1845709" - integrity sha512-I8gNHJLIc7GdApm7wkVnStWssPNbSRMPtgHdmH3sRM1zopz09UWPS4x5V4n1yz/MIWTVnJ9sp6IkuXdWM4w+2Q== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-transform-unicode-regex@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.12.1.tgz#cc9661f61390db5c65e3febaccefd5c6ac3faecb" - integrity sha512-SqH4ClNngh/zGwHZOOQMTD+e8FGWexILV+ePMyiDJttAWRh5dhDL8rcl5lSgU3Huiq6Zn6pWTMvdPAb21Dwdyg== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.12.1" - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/preset-env@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.12.1.tgz#9c7e5ca82a19efc865384bb4989148d2ee5d7ac2" - integrity sha512-H8kxXmtPaAGT7TyBvSSkoSTUK6RHh61So05SyEbpmr0MCZrsNYn7mGMzzeYoOUCdHzww61k8XBft2TaES+xPLg== - dependencies: - "@babel/compat-data" "^7.12.1" - "@babel/helper-compilation-targets" "^7.12.1" - "@babel/helper-module-imports" "^7.12.1" - "@babel/helper-plugin-utils" "^7.10.4" - "@babel/helper-validator-option" "^7.12.1" - "@babel/plugin-proposal-async-generator-functions" "^7.12.1" - "@babel/plugin-proposal-class-properties" "^7.12.1" - "@babel/plugin-proposal-dynamic-import" "^7.12.1" - "@babel/plugin-proposal-export-namespace-from" "^7.12.1" - "@babel/plugin-proposal-json-strings" "^7.12.1" - "@babel/plugin-proposal-logical-assignment-operators" "^7.12.1" - "@babel/plugin-proposal-nullish-coalescing-operator" "^7.12.1" - "@babel/plugin-proposal-numeric-separator" "^7.12.1" - "@babel/plugin-proposal-object-rest-spread" "^7.12.1" - "@babel/plugin-proposal-optional-catch-binding" "^7.12.1" - "@babel/plugin-proposal-optional-chaining" "^7.12.1" - "@babel/plugin-proposal-private-methods" "^7.12.1" - "@babel/plugin-proposal-unicode-property-regex" "^7.12.1" - "@babel/plugin-syntax-async-generators" "^7.8.0" - "@babel/plugin-syntax-class-properties" "^7.12.1" - "@babel/plugin-syntax-dynamic-import" "^7.8.0" - "@babel/plugin-syntax-export-namespace-from" "^7.8.3" - "@babel/plugin-syntax-json-strings" "^7.8.0" - "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" - "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0" - "@babel/plugin-syntax-numeric-separator" "^7.10.4" - "@babel/plugin-syntax-object-rest-spread" "^7.8.0" - "@babel/plugin-syntax-optional-catch-binding" "^7.8.0" - "@babel/plugin-syntax-optional-chaining" "^7.8.0" - "@babel/plugin-syntax-top-level-await" "^7.12.1" - "@babel/plugin-transform-arrow-functions" "^7.12.1" - "@babel/plugin-transform-async-to-generator" "^7.12.1" - "@babel/plugin-transform-block-scoped-functions" "^7.12.1" - "@babel/plugin-transform-block-scoping" "^7.12.1" - "@babel/plugin-transform-classes" "^7.12.1" - "@babel/plugin-transform-computed-properties" "^7.12.1" - "@babel/plugin-transform-destructuring" "^7.12.1" - "@babel/plugin-transform-dotall-regex" "^7.12.1" - "@babel/plugin-transform-duplicate-keys" "^7.12.1" - "@babel/plugin-transform-exponentiation-operator" "^7.12.1" - "@babel/plugin-transform-for-of" "^7.12.1" - "@babel/plugin-transform-function-name" "^7.12.1" - "@babel/plugin-transform-literals" "^7.12.1" - "@babel/plugin-transform-member-expression-literals" "^7.12.1" - "@babel/plugin-transform-modules-amd" "^7.12.1" - "@babel/plugin-transform-modules-commonjs" "^7.12.1" - "@babel/plugin-transform-modules-systemjs" "^7.12.1" - "@babel/plugin-transform-modules-umd" "^7.12.1" - "@babel/plugin-transform-named-capturing-groups-regex" "^7.12.1" - "@babel/plugin-transform-new-target" "^7.12.1" - "@babel/plugin-transform-object-super" "^7.12.1" - "@babel/plugin-transform-parameters" "^7.12.1" - "@babel/plugin-transform-property-literals" "^7.12.1" - "@babel/plugin-transform-regenerator" "^7.12.1" - "@babel/plugin-transform-reserved-words" "^7.12.1" - "@babel/plugin-transform-shorthand-properties" "^7.12.1" - "@babel/plugin-transform-spread" "^7.12.1" - "@babel/plugin-transform-sticky-regex" "^7.12.1" - "@babel/plugin-transform-template-literals" "^7.12.1" - "@babel/plugin-transform-typeof-symbol" "^7.12.1" - "@babel/plugin-transform-unicode-escapes" "^7.12.1" - "@babel/plugin-transform-unicode-regex" "^7.12.1" - "@babel/preset-modules" "^0.1.3" - "@babel/types" "^7.12.1" - core-js-compat "^3.6.2" - semver "^5.5.0" - -"@babel/preset-modules@^0.1.3": - version "0.1.4" - resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.4.tgz#362f2b68c662842970fdb5e254ffc8fc1c2e415e" - integrity sha512-J36NhwnfdzpmH41M1DrnkkgAqhZaqr/NBdPfQ677mLzlaXo+oDiv1deyCDtgAhz8p328otdob0Du7+xgHGZbKg== +"@codemirror/lang-xml@^6.0.0": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@codemirror/lang-xml/-/lang-xml-6.0.1.tgz#ac2dd701d26683163543248b5abc56829ba7fcc6" + integrity sha512-0tvycUTElajCcRKgsszhKjWX+uuOogdu5+enpfqYA+j0gnP8ek7LRxujh2/XMPRdXt/hwOML4slJLE7r2eX3yQ== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@babel/plugin-proposal-unicode-property-regex" "^7.4.4" - "@babel/plugin-transform-dotall-regex" "^7.4.4" - "@babel/types" "^7.4.4" - esutils "^2.0.2" + "@codemirror/autocomplete" "^6.0.0" + "@codemirror/language" "^6.0.0" + "@codemirror/state" "^6.0.0" + "@lezer/common" "^1.0.0" + "@lezer/xml" "^1.0.0" -"@babel/preset-react@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.12.1.tgz#7f022b13f55b6dd82f00f16d1c599ae62985358c" - integrity sha512-euCExymHCi0qB9u5fKw7rvlw7AZSjw/NaB9h7EkdTt5+yHRrXdiRTh7fkG3uBPpJg82CqLfp1LHLqWGSCrab+g== +"@codemirror/language-data@^6.0.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@codemirror/language-data/-/language-data-6.1.0.tgz#479eff66289a6453493f7c8213d7b2ceb95c89f6" + integrity sha512-g9V23fuLRI9AEbpM6bDy1oquqgpFlIDHTihUhL21NPmxp+x67ZJbsKk+V71W7/Bj8SCqEO1PtqQA/tDGgt1nfw== + dependencies: + "@codemirror/lang-cpp" "^6.0.0" + "@codemirror/lang-css" "^6.0.0" + "@codemirror/lang-html" "^6.0.0" + "@codemirror/lang-java" "^6.0.0" + "@codemirror/lang-javascript" "^6.0.0" + "@codemirror/lang-json" "^6.0.0" + "@codemirror/lang-markdown" "^6.0.0" + "@codemirror/lang-php" "^6.0.0" + "@codemirror/lang-python" "^6.0.0" + "@codemirror/lang-rust" "^6.0.0" + "@codemirror/lang-sql" "^6.0.0" + "@codemirror/lang-wast" "^6.0.0" + "@codemirror/lang-xml" "^6.0.0" + "@codemirror/language" "^6.0.0" + "@codemirror/legacy-modes" "^6.1.0" + +"@codemirror/language@^6.0.0", "@codemirror/language@^6.3.0": + version "6.3.0" + resolved "https://registry.yarnpkg.com/@codemirror/language/-/language-6.3.0.tgz#141c715e1fce5f6dcca3b1b984ed8f03f583dd5c" + integrity sha512-6jOE5DEt6sKD46SXhn3xPbBehn+l48ACcA6Uxs2k+E2YNH9XGF5WdGMTYr2DlggfK4h0QZBK6zEb5S7lkTriWA== dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - "@babel/plugin-transform-react-display-name" "^7.12.1" - "@babel/plugin-transform-react-jsx" "^7.12.1" - "@babel/plugin-transform-react-jsx-development" "^7.12.1" - "@babel/plugin-transform-react-jsx-self" "^7.12.1" - "@babel/plugin-transform-react-jsx-source" "^7.12.1" - "@babel/plugin-transform-react-pure-annotations" "^7.12.1" + "@codemirror/state" "^6.0.0" + "@codemirror/view" "^6.0.0" + "@lezer/common" "^1.0.0" + "@lezer/highlight" "^1.0.0" + "@lezer/lr" "^1.0.0" + style-mod "^4.0.0" -"@babel/preset-typescript@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.12.1.tgz#86480b483bb97f75036e8864fe404cc782cc311b" - integrity sha512-hNK/DhmoJPsksdHuI/RVrcEws7GN5eamhi28JkO52MqIxU8Z0QpmiSOQxZHWOHV7I3P4UjHV97ay4TcamMA6Kw== +"@codemirror/legacy-modes@^6.0.0", "@codemirror/legacy-modes@^6.1.0": + version "6.2.0" + resolved "https://registry.yarnpkg.com/@codemirror/legacy-modes/-/legacy-modes-6.2.0.tgz#473016ccafba0990f23c981b678843194be5e131" + integrity sha512-RtZfwALTSswzKsnU3zo5FytDBA+/6J85Z8qNO7hnJ3Lo+jbdKUHreiw5m9yAT1DoB5WFhSZODsSijksdSbnGqA== dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - "@babel/plugin-transform-typescript" "^7.12.1" + "@codemirror/language" "^6.0.0" -"@babel/runtime@^7.1.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.16.3", "@babel/runtime@^7.17.2", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2", "@babel/runtime@^7.9.6": - version "7.18.3" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.18.3.tgz#c7b654b57f6f63cf7f8b418ac9ca04408c4579f4" - integrity sha512-38Y8f7YUhce/K7RMwTp7m0uCumpv9hZkitCbBClqQIow1qSbCvGkcegKOXpEWCQLfWmevgRiWokZ1GkpfhbZug== +"@codemirror/lint@^6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@codemirror/lint/-/lint-6.0.0.tgz#a249b021ac9933b94fe312d994d220f0ef11a157" + integrity sha512-nUUXcJW1Xp54kNs+a1ToPLK8MadO0rMTnJB8Zk4Z8gBdrN0kqV7uvUraU/T2yqg+grDNR38Vmy/MrhQN/RgwiA== dependencies: - regenerator-runtime "^0.13.4" + "@codemirror/state" "^6.0.0" + "@codemirror/view" "^6.0.0" + crelt "^1.0.5" -"@babel/template@^7.10.4", "@babel/template@^7.14.5", "@babel/template@^7.16.7", "@babel/template@^7.3.3": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.16.7.tgz#8d126c8701fde4d66b264b3eba3d96f07666d155" - integrity sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w== +"@codemirror/search@^6.0.0": + version "6.2.2" + resolved "https://registry.yarnpkg.com/@codemirror/search/-/search-6.2.2.tgz#278ac204bd19a038271595ce060ad32c13eb70a6" + integrity sha512-2pWY599zXk+lSoJ2iv9EuTO4gB7lhgBPLPwFb/zTbimFH4NmZSaKzJSV51okjABZ7/Rj0DYy5klWbIgaJh2LoQ== dependencies: - "@babel/code-frame" "^7.16.7" - "@babel/parser" "^7.16.7" - "@babel/types" "^7.16.7" + "@codemirror/state" "^6.0.0" + "@codemirror/view" "^6.0.0" + crelt "^1.0.5" -"@babel/traverse@^7.10.4", "@babel/traverse@^7.12.1", "@babel/traverse@^7.18.0", "@babel/traverse@^7.18.2", "@babel/traverse@^7.18.5", "@babel/traverse@^7.4.5", "@babel/traverse@^7.7.2": - version "7.18.5" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.18.5.tgz#94a8195ad9642801837988ab77f36e992d9a20cd" - integrity sha512-aKXj1KT66sBj0vVzk6rEeAO6Z9aiiQ68wfDgge3nHhA/my6xMM/7HGQUNumKZaoa2qUPQ5whJG9aAifsxUKfLA== - dependencies: - "@babel/code-frame" "^7.16.7" - "@babel/generator" "^7.18.2" - "@babel/helper-environment-visitor" "^7.18.2" - "@babel/helper-function-name" "^7.17.9" - "@babel/helper-hoist-variables" "^7.16.7" - "@babel/helper-split-export-declaration" "^7.16.7" - "@babel/parser" "^7.18.5" - "@babel/types" "^7.18.4" - debug "^4.1.0" - globals "^11.1.0" +"@codemirror/state@^6.0.0": + version "6.1.2" + resolved "https://registry.yarnpkg.com/@codemirror/state/-/state-6.1.2.tgz#182d46eabcc17c95508984d6add5a5a641dcd517" + integrity sha512-Mxff85Hp5va+zuj+H748KbubXjrinX/k28lj43H14T2D0+4kuvEFIEIO7hCEcvBT8ubZyIelt9yGOjj2MWOEQA== -"@babel/types@^7.0.0", "@babel/types@^7.10.4", "@babel/types@^7.10.5", "@babel/types@^7.12.1", "@babel/types@^7.16.7", "@babel/types@^7.17.0", "@babel/types@^7.18.0", "@babel/types@^7.18.2", "@babel/types@^7.18.4", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4": - version "7.18.4" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.18.4.tgz#27eae9b9fd18e9dccc3f9d6ad051336f307be354" - integrity sha512-ThN1mBcMq5pG/Vm2IcBmPPfyPXbd8S02rS+OBIDENdufvqC7Z/jHPCv9IcP01277aKtDI8g/2XysBN4hA8niiw== +"@codemirror/view@^6.0.0", "@codemirror/view@^6.2.2": + version "6.4.0" + resolved "https://registry.yarnpkg.com/@codemirror/view/-/view-6.4.0.tgz#f8c213ab6dfec56048b372d2c378213428e2b4e5" + integrity sha512-Kv32b6Tn7QVwFbj/EDswTLSocjk5kgggF6zzBFAL4o4hZ/vmtFD155+EjH1pVlbfoDyVC2M6SedPsMrwYscgNg== dependencies: - "@babel/helper-validator-identifier" "^7.16.7" - to-fast-properties "^2.0.0" + "@codemirror/state" "^6.0.0" + style-mod "^4.0.0" + w3c-keyname "^2.2.4" -"@bcoe/v8-coverage@^0.2.3": - version "0.2.3" - resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" - integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== - -"@csstools/postcss-cascade-layers@^1.0.2": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@csstools/postcss-cascade-layers/-/postcss-cascade-layers-1.0.3.tgz#71ee4a3f00f947788097f8d67310b2e4a336aa51" - integrity sha512-fvXP0+dcllGtRKAjA5n5tBr57xWQalKky09hSiXAZ9qqjHn0sDuQV2Jz0Y5zHRQ6iGrAjJZOf2+xQj3yuXfLwA== +"@csstools/postcss-cascade-layers@^1.1.0": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@csstools/postcss-cascade-layers/-/postcss-cascade-layers-1.1.1.tgz#8a997edf97d34071dd2e37ea6022447dd9e795ad" + integrity sha512-+KdYrpKC5TgomQr2DlZF4lDEpHcoxnj5IGddYYfBWJAKfj1JtuHUIqMa+E1pJJ+z3kvDViWMqyqPlG4Ja7amQA== dependencies: - "@csstools/selector-specificity" "^2.0.0" + "@csstools/selector-specificity" "^2.0.2" postcss-selector-parser "^6.0.10" -"@csstools/postcss-color-function@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@csstools/postcss-color-function/-/postcss-color-function-1.1.0.tgz#229966327747f58fbe586de35daa139db3ce1e5d" - integrity sha512-5D5ND/mZWcQoSfYnSPsXtuiFxhzmhxt6pcjrFLJyldj+p0ZN2vvRpYNX+lahFTtMhAYOa2WmkdGINr0yP0CvGA== +"@csstools/postcss-color-function@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@csstools/postcss-color-function/-/postcss-color-function-1.1.1.tgz#2bd36ab34f82d0497cfacdc9b18d34b5e6f64b6b" + integrity sha512-Bc0f62WmHdtRDjf5f3e2STwRAl89N2CLb+9iAwzrv4L2hncrbDwnQD9PCq0gtAt7pOI2leIV08HIBUd4jxD8cw== dependencies: "@csstools/postcss-progressive-custom-properties" "^1.1.0" postcss-value-parser "^4.2.0" -"@csstools/postcss-font-format-keywords@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@csstools/postcss-font-format-keywords/-/postcss-font-format-keywords-1.0.0.tgz#7e7df948a83a0dfb7eb150a96e2390ac642356a1" - integrity sha512-oO0cZt8do8FdVBX8INftvIA4lUrKUSCcWUf9IwH9IPWOgKT22oAZFXeHLoDK7nhB2SmkNycp5brxfNMRLIhd6Q== +"@csstools/postcss-font-format-keywords@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@csstools/postcss-font-format-keywords/-/postcss-font-format-keywords-1.0.1.tgz#677b34e9e88ae997a67283311657973150e8b16a" + integrity sha512-ZgrlzuUAjXIOc2JueK0X5sZDjCtgimVp/O5CEqTcs5ShWBa6smhWYbS0x5cVc/+rycTDbjjzoP0KTDnUneZGOg== dependencies: postcss-value-parser "^4.2.0" -"@csstools/postcss-hwb-function@^1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@csstools/postcss-hwb-function/-/postcss-hwb-function-1.0.1.tgz#5224db711ed09a965f85c80c18144ac1c2702fce" - integrity sha512-AMZwWyHbbNLBsDADWmoXT9A5yl5dsGEBeJSJRUJt8Y9n8Ziu7Wstt4MC8jtPW7xjcLecyfJwtnUTNSmOzcnWeg== +"@csstools/postcss-hwb-function@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@csstools/postcss-hwb-function/-/postcss-hwb-function-1.0.2.tgz#ab54a9fce0ac102c754854769962f2422ae8aa8b" + integrity sha512-YHdEru4o3Rsbjmu6vHy4UKOXZD+Rn2zmkAmLRfPet6+Jz4Ojw8cbWxe1n42VaXQhD3CQUXXTooIy8OkVbUcL+w== dependencies: postcss-value-parser "^4.2.0" -"@csstools/postcss-ic-unit@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@csstools/postcss-ic-unit/-/postcss-ic-unit-1.0.0.tgz#f484db59fc94f35a21b6d680d23b0ec69b286b7f" - integrity sha512-i4yps1mBp2ijrx7E96RXrQXQQHm6F4ym1TOD0D69/sjDjZvQ22tqiEvaNw7pFZTUO5b9vWRHzbHzP9+UKuw+bA== +"@csstools/postcss-ic-unit@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@csstools/postcss-ic-unit/-/postcss-ic-unit-1.0.1.tgz#28237d812a124d1a16a5acc5c3832b040b303e58" + integrity sha512-Ot1rcwRAaRHNKC9tAqoqNZhjdYBzKk1POgWfhN4uCOE47ebGcLRqXjKkApVDpjifL6u2/55ekkpnFcp+s/OZUw== dependencies: "@csstools/postcss-progressive-custom-properties" "^1.1.0" postcss-value-parser "^4.2.0" -"@csstools/postcss-is-pseudo-class@^2.0.4": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-2.0.5.tgz#60fea78776fc3916ad66d568064aa31029b9f772" - integrity sha512-Ek+UFI4UP2hB9u0N1cJd6KgSF1rL0J3PT4is0oSStuus8+WzbGGPyJNMOKQ0w/tyPjxiCnOI4RdSMZt3nks64g== +"@csstools/postcss-is-pseudo-class@^2.0.7": + version "2.0.7" + resolved "https://registry.yarnpkg.com/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-2.0.7.tgz#846ae6c0d5a1eaa878fce352c544f9c295509cd1" + integrity sha512-7JPeVVZHd+jxYdULl87lvjgvWldYu+Bc62s9vD/ED6/QTGjy0jy0US/f6BG53sVMTBJ1lzKZFpYmofBN9eaRiA== dependencies: "@csstools/selector-specificity" "^2.0.0" postcss-selector-parser "^6.0.10" -"@csstools/postcss-normalize-display-values@^1.0.0": +"@csstools/postcss-nested-calc@^1.0.0": version "1.0.0" - resolved "https://registry.yarnpkg.com/@csstools/postcss-normalize-display-values/-/postcss-normalize-display-values-1.0.0.tgz#ce698f688c28517447aedf15a9037987e3d2dc97" - integrity sha512-bX+nx5V8XTJEmGtpWTO6kywdS725t71YSLlxWt78XoHUbELWgoCXeOFymRJmL3SU1TLlKSIi7v52EWqe60vJTQ== + resolved "https://registry.yarnpkg.com/@csstools/postcss-nested-calc/-/postcss-nested-calc-1.0.0.tgz#d7e9d1d0d3d15cf5ac891b16028af2a1044d0c26" + integrity sha512-JCsQsw1wjYwv1bJmgjKSoZNvf7R6+wuHDAbi5f/7MbFhl2d/+v+TvBTU4BJH3G1X1H87dHl0mh6TfYogbT/dJQ== dependencies: postcss-value-parser "^4.2.0" -"@csstools/postcss-oklab-function@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@csstools/postcss-oklab-function/-/postcss-oklab-function-1.1.0.tgz#e9a269487a292e0930760948e923e1d46b638ee6" - integrity sha512-e/Q5HopQzmnQgqimG9v3w2IG4VRABsBq3itOcn4bnm+j4enTgQZ0nWsaH/m9GV2otWGQ0nwccYL5vmLKyvP1ww== +"@csstools/postcss-normalize-display-values@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@csstools/postcss-normalize-display-values/-/postcss-normalize-display-values-1.0.1.tgz#15da54a36e867b3ac5163ee12c1d7f82d4d612c3" + integrity sha512-jcOanIbv55OFKQ3sYeFD/T0Ti7AMXc9nM1hZWu8m/2722gOTxFg7xYu4RDLJLeZmPUVQlGzo4jhzvTUq3x4ZUw== + dependencies: + postcss-value-parser "^4.2.0" + +"@csstools/postcss-oklab-function@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@csstools/postcss-oklab-function/-/postcss-oklab-function-1.1.1.tgz#88cee0fbc8d6df27079ebd2fa016ee261eecf844" + integrity sha512-nJpJgsdA3dA9y5pgyb/UfEzE7W5Ka7u0CX0/HIMVBNWzWemdcTH3XwANECU6anWv/ao4vVNLTMxhiPNZsTK6iA== dependencies: "@csstools/postcss-progressive-custom-properties" "^1.1.0" postcss-value-parser "^4.2.0" @@ -1044,42 +662,66 @@ dependencies: postcss-value-parser "^4.2.0" -"@csstools/postcss-stepped-value-functions@^1.0.0": +"@csstools/postcss-stepped-value-functions@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@csstools/postcss-stepped-value-functions/-/postcss-stepped-value-functions-1.0.1.tgz#f8772c3681cc2befed695e2b0b1d68e22f08c4f4" + integrity sha512-dz0LNoo3ijpTOQqEJLY8nyaapl6umbmDcgj4AD0lgVQ572b2eqA1iGZYTTWhrcrHztWDDRAX2DGYyw2VBjvCvQ== + dependencies: + postcss-value-parser "^4.2.0" + +"@csstools/postcss-text-decoration-shorthand@^1.0.0": version "1.0.0" - resolved "https://registry.yarnpkg.com/@csstools/postcss-stepped-value-functions/-/postcss-stepped-value-functions-1.0.0.tgz#f8ffc05e163ba7bcbefc5fdcaf264ce9fd408c16" - integrity sha512-q8c4bs1GumAiRenmFjASBcWSLKrbzHzWl6C2HcaAxAXIiL2rUlUWbqQZUjwVG5tied0rld19j/Mm90K3qI26vw== + resolved "https://registry.yarnpkg.com/@csstools/postcss-text-decoration-shorthand/-/postcss-text-decoration-shorthand-1.0.0.tgz#ea96cfbc87d921eca914d3ad29340d9bcc4c953f" + integrity sha512-c1XwKJ2eMIWrzQenN0XbcfzckOLLJiczqy+YvfGmzoVXd7pT9FfObiSEfzs84bpE/VqfpEuAZ9tCRbZkZxxbdw== dependencies: postcss-value-parser "^4.2.0" -"@csstools/postcss-trigonometric-functions@^1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@csstools/postcss-trigonometric-functions/-/postcss-trigonometric-functions-1.0.1.tgz#e36e61f445614193dbf6d3a8408709b0cf184a6f" - integrity sha512-G78CY/+GePc6dDCTUbwI6TTFQ5fs3N9POHhI6v0QzteGpf6ylARiJUNz9HrRKi4eVYBNXjae1W2766iUEFxHlw== +"@csstools/postcss-trigonometric-functions@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@csstools/postcss-trigonometric-functions/-/postcss-trigonometric-functions-1.0.2.tgz#94d3e4774c36d35dcdc88ce091336cb770d32756" + integrity sha512-woKaLO///4bb+zZC2s80l+7cm07M7268MsyG3M0ActXXEFi6SuhvriQYcb58iiKGbjwwIU7n45iRLEHypB47Og== dependencies: postcss-value-parser "^4.2.0" -"@csstools/postcss-unset-value@^1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@csstools/postcss-unset-value/-/postcss-unset-value-1.0.1.tgz#2cc020785db5ec82cc9444afe4cdae2a65445f89" - integrity sha512-f1G1WGDXEU/RN1TWAxBPQgQudtLnLQPyiWdtypkPC+mVYNKFKH/HYXSxH4MVNqwF8M0eDsoiU7HumJHCg/L/jg== +"@csstools/postcss-unset-value@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@csstools/postcss-unset-value/-/postcss-unset-value-1.0.2.tgz#c99bb70e2cdc7312948d1eb41df2412330b81f77" + integrity sha512-c8J4roPBILnelAsdLr4XOAR/GsTm0GJi4XpcfvoWk3U6KiTCqiFYc63KhRMQQX35jYMp4Ao8Ij9+IZRgMfJp1g== "@csstools/selector-specificity@^2.0.0": version "2.0.0" resolved "https://registry.yarnpkg.com/@csstools/selector-specificity/-/selector-specificity-2.0.0.tgz#65b12f12db55188422070e34687bf3af09870922" integrity sha512-rZ6vufeY/UjAgtyiJ4WvfF6XP6HizIyOfbZOg0RnecIwjrvH8Am3nN1BpKnnPZunYAkUcPPXDhwbxOtGop8cfQ== -"@emotion/is-prop-valid@^0.8.2", "@emotion/is-prop-valid@^0.8.8": +"@csstools/selector-specificity@^2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@csstools/selector-specificity/-/selector-specificity-2.0.2.tgz#1bfafe4b7ed0f3e4105837e056e0a89b108ebe36" + integrity sha512-IkpVW/ehM1hWKln4fCA3NzJU8KwD+kIOvPZA4cqxoJHtE21CCzjyp+Kxbu0i5I4tBNOlXPL9mjwnWlL0VEG4Fg== + +"@emotion/is-prop-valid@^0.8.2": version "0.8.8" resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz#db28b1c4368a259b60a97311d6a952d4fd01ac1a" integrity sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA== dependencies: "@emotion/memoize" "0.7.4" +"@emotion/is-prop-valid@^1.1.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-1.2.0.tgz#7f2d35c97891669f7e276eb71c83376a5dc44c83" + integrity sha512-3aDpDprjM0AwaxGE09bOPkNxHpBd+kA6jty3RnaEXdweX1DF1U3VQpPYb0g1IStAuK7SVQ1cy+bNBBKp4W3Fjg== + dependencies: + "@emotion/memoize" "^0.8.0" + "@emotion/memoize@0.7.4": version "0.7.4" resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.7.4.tgz#19bf0f5af19149111c40d98bb0cf82119f5d9eeb" integrity sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw== +"@emotion/memoize@^0.8.0": + version "0.8.0" + resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.8.0.tgz#f580f9beb67176fa57aae70b08ed510e1b18980f" + integrity sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA== + "@emotion/stylis@^0.8.4": version "0.8.5" resolved "https://registry.yarnpkg.com/@emotion/stylis/-/stylis-0.8.5.tgz#deacb389bd6ee77d1e7fcaccce9e16c5c7e78e04" @@ -1090,6 +732,16 @@ resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.7.5.tgz#77211291c1900a700b8a78cfafda3160d76949ed" integrity sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg== +"@esbuild/android-arm@0.15.12": + version "0.15.12" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.15.12.tgz#e548b10a5e55b9e10537a049ebf0bc72c453b769" + integrity sha512-IC7TqIqiyE0MmvAhWkl/8AEzpOtbhRNDo7aph47We1NbE5w2bt/Q+giAhe0YYeVpYnIhGMcuZY92qDK6dQauvA== + +"@esbuild/linux-loong64@0.15.12": + version "0.15.12" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.15.12.tgz#475b33a2631a3d8ca8aa95ee127f9a61d95bf9c1" + integrity sha512-tZEowDjvU7O7I04GYvWQOS4yyP9E/7YlsB0jjw1Ycukgr2ycEzKyIk5tms5WnLBymaewc6VmRKnn5IJWgK4eFw== + "@eslint/eslintrc@^1.3.0": version "1.3.0" resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.3.0.tgz#29f92c30bb3e771e4a2048c95fa6855392dfac4f" @@ -1105,86 +757,76 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@floating-ui/core@^0.7.3": - version "0.7.3" - resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-0.7.3.tgz#d274116678ffae87f6b60e90f88cc4083eefab86" - integrity sha512-buc8BXHmG9l82+OQXOFU3Kr2XQx9ys01U/Q9HMIrZ300iLc8HLMgh7dcCqgYzAzf4BkoQvDcXf5Y+CuEZ5JBYg== +"@floating-ui/core@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.0.1.tgz#00e64d74e911602c8533957af0cce5af6b2e93c8" + integrity sha512-bO37brCPfteXQfFY0DyNDGB3+IMe4j150KFQcgJ5aBP295p9nBGeHEs/p0czrRbtlHq4Px/yoPXO/+dOCcF4uA== -"@floating-ui/dom@^0.5.3": - version "0.5.4" - resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-0.5.4.tgz#4eae73f78bcd4bd553ae2ade30e6f1f9c73fe3f1" - integrity sha512-419BMceRLq0RrmTSDxn8hf9R3VCJv2K9PUfugh5JyEFmdjzDo+e8U5EdR8nzKq8Yj1htzLm3b6eQEEam3/rrtg== +"@floating-ui/dom@^1.0.0": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.0.3.tgz#b439c8a66436c2cae8d97e889f0b76cce757a6ec" + integrity sha512-6H1kwjkOZKabApNtXRiYHvMmYJToJ1DV7rQ3xc/WJpOABhQIOJJOdz2AOejj8X+gcybaFmBpisVTZxBZAM3V0w== dependencies: - "@floating-ui/core" "^0.7.3" + "@floating-ui/core" "^1.0.1" -"@floating-ui/react-dom-interactions@^0.6.6": - version "0.6.6" - resolved "https://registry.yarnpkg.com/@floating-ui/react-dom-interactions/-/react-dom-interactions-0.6.6.tgz#8542e8c4bcbee2cd0d512de676c6a493e0a2d168" - integrity sha512-qnao6UPjSZNHnXrF+u4/n92qVroQkx0Umlhy3Avk1oIebm/5ee6yvDm4xbHob0OjY7ya8WmUnV3rQlPwX3Atwg== +"@floating-ui/react-dom-interactions@0.10.2": + version "0.10.2" + resolved "https://registry.yarnpkg.com/@floating-ui/react-dom-interactions/-/react-dom-interactions-0.10.2.tgz#1a9c86f8bb9aa36b5926ae03d96de78579d2a70d" + integrity sha512-KhF+UN+MVqUx1bG1fe0aAiBl1hbz07Uin6UW70mxwUDhaGpitM16CYvGri1EqGY4hnWK8TQknDSP8iQFOxjhsg== dependencies: - "@floating-ui/react-dom" "^0.7.2" + "@floating-ui/react-dom" "^1.0.0" aria-hidden "^1.1.3" - use-isomorphic-layout-effect "^1.1.1" -"@floating-ui/react-dom@^0.7.2": - version "0.7.2" - resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-0.7.2.tgz#0bf4ceccb777a140fc535c87eb5d6241c8e89864" - integrity sha512-1T0sJcpHgX/u4I1OzIEhlcrvkUN8ln39nz7fMoE/2HDHrPiMFoOGR7++GYyfUmIQHkkrTinaeQsO3XWubjSvGg== +"@floating-ui/react-dom@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-1.0.0.tgz#e0975966694433f1f0abffeee5d8e6bb69b7d16e" + integrity sha512-uiOalFKPG937UCLm42RxjESTWUVpbbatvlphQAU6bsv+ence6IoVG8JOUZcy8eW81NkU+Idiwvx10WFLmR4MIg== + dependencies: + "@floating-ui/dom" "^1.0.0" + +"@flyyer/use-fit-text@3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@flyyer/use-fit-text/-/use-fit-text-3.0.1.tgz#99bdcc59f4fd6659c55061dec0deb134305af7aa" + integrity sha512-bE51XCTURJrRDUCkWUsRKNT8vhzl0Ivar8T2yD43MTE3Q+fzDd+iZxy77HrqycwG35ykQdjua3cRZkDhznNboA== dependencies: - "@floating-ui/dom" "^0.5.3" - use-isomorphic-layout-effect "^1.1.1" + dequal "^2.0.2" -"@fortawesome/fontawesome-common-types@^0.2.32": - version "0.2.32" - resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.32.tgz#3436795d5684f22742989bfa08f46f50f516f259" - integrity sha512-ux2EDjKMpcdHBVLi/eWZynnPxs0BtFVXJkgHIxXRl+9ZFaHPvYamAfCzeeQFqHRjuJtX90wVnMRaMQAAlctz3w== +"@fortawesome/fontawesome-common-types@6.2.0": + version "6.2.0" + resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.2.0.tgz#76467a94aa888aeb22aafa43eb6ff889df3a5a7f" + integrity sha512-rBevIsj2nclStJ7AxTdfsa3ovHb1H+qApwrxcTVo+NNdeJiB9V75hsKfrkG5AwNcRUNxrPPiScGYCNmLMoh8pg== -"@fortawesome/fontawesome-svg-core@^1.2.32": - version "1.2.32" - resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.32.tgz#da092bfc7266aa274be8604de610d7115f9ba6cf" - integrity sha512-XjqyeLCsR/c/usUpdWcOdVtWFVjPbDFBTQkn2fQRrWhhUoxriQohO2RWDxLyUM8XpD+Zzg5xwJ8gqTYGDLeGaQ== +"@fortawesome/fontawesome-svg-core@6.2.0": + version "6.2.0" + resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.2.0.tgz#11856eaf4dd1d865c442ddea1eed8ee855186ba2" + integrity sha512-Cf2mAAeMWFMzpLC7Y9H1I4o3wEU+XovVJhTiNG8ZNgSQj53yl7OCJaS80K4YjrABWZzbAHVaoHE1dVJ27AAYXw== dependencies: - "@fortawesome/fontawesome-common-types" "^0.2.32" + "@fortawesome/fontawesome-common-types" "6.2.0" -"@fortawesome/free-solid-svg-icons@^5.15.1": - version "5.15.1" - resolved "https://registry.yarnpkg.com/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.15.1.tgz#e1432676ddd43108b41197fee9f86d910ad458ef" - integrity sha512-EFMuKtzRMNbvjab/SvJBaOOpaqJfdSap/Nl6hst7CgrJxwfORR1drdTV6q1Ib/JVzq4xObdTDcT6sqTaXMqfdg== +"@fortawesome/free-solid-svg-icons@6.2.0": + version "6.2.0" + resolved "https://registry.yarnpkg.com/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.2.0.tgz#8dcde48109354fd7a5ece8ea48d678bb91d4b5f0" + integrity sha512-UjCILHIQ4I8cN46EiQn0CZL/h8AwCGgR//1c4R96Q5viSRwuKVo0NdQEc4bm+69ZwC0dUvjbDqAHF1RR5FA3XA== dependencies: - "@fortawesome/fontawesome-common-types" "^0.2.32" + "@fortawesome/fontawesome-common-types" "6.2.0" -"@fortawesome/react-fontawesome@^0.1.11": - version "0.1.11" - resolved "https://registry.yarnpkg.com/@fortawesome/react-fontawesome/-/react-fontawesome-0.1.11.tgz#c1a95a2bdb6a18fa97b355a563832e248bf6ef4a" - integrity sha512-sClfojasRifQKI0OPqTy8Ln8iIhnxR/Pv/hukBhWnBz9kQRmqi6JSH3nghlhAY7SUeIIM7B5/D2G8WjX0iepVg== +"@fortawesome/react-fontawesome@0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.0.tgz#d90dd8a9211830b4e3c08e94b63a0ba7291ddcf4" + integrity sha512-uHg75Rb/XORTtVt7OS9WoK8uM276Ufi7gCzshVWkUJbHhh3svsUUeqXerrM96Wm7fRiDzfKRwSoahhMIkGAYHw== dependencies: - prop-types "^15.7.2" + prop-types "^15.8.1" -"@gar/promisify@^1.0.1": - version "1.1.3" - resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6" - integrity sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw== - -"@headlessui/react@^1.6.4": - version "1.6.4" - resolved "https://registry.yarnpkg.com/@headlessui/react/-/react-1.6.4.tgz#c73084e23386bef5fb86cd16da3352c3a844bb4c" - integrity sha512-0yqz1scwbFtwljmbbKjXsSGl5ABEYNICVHZnMCWo0UtOZodo2Tpu94uOVgCRjRZ77l2WcTi2S0uidINDvG7lsA== +"@headlessui/react@1.7.3": + version "1.7.3" + resolved "https://registry.yarnpkg.com/@headlessui/react/-/react-1.7.3.tgz#853c598ff47b37cdd192c5cbee890d9b610c3ec0" + integrity sha512-LGp06SrGv7BMaIQlTs8s2G06moqkI0cb0b8stgq7KZ3xcHdH3qMP+cRyV7qe5x4XEW/IGY48BW4fLesD6NQLng== -"@heroicons/react@^1.0.6": +"@heroicons/react@1.0.6": version "1.0.6" resolved "https://registry.yarnpkg.com/@heroicons/react/-/react-1.0.6.tgz#35dd26987228b39ef2316db3b1245c42eb19e324" integrity sha512-JJCXydOFWMDpCP4q13iEplA503MQO3xLoZiKum+955ZCtHINWnx26CUxVxxFQu/uLb4LW3ge15ZpzIkXKkJ8oQ== -"@hot-loader/react-dom@^16.14.0": - version "16.14.0" - resolved "https://registry.yarnpkg.com/@hot-loader/react-dom/-/react-dom-16.14.0.tgz#3cfc64e40bb78fa623e59b582b8f09dcdaad648a" - integrity sha512-EN9czvcLsMYmSDo5yRKZOAq3ZGRlDpad1gPtX0NdMMomJXcPE3yFSeFzE94X/NjOaiSVimB7LuqPYpkWVaIi4Q== - dependencies: - loose-envify "^1.1.0" - object-assign "^4.1.1" - prop-types "^15.6.2" - scheduler "^0.19.1" - "@humanwhocodes/config-array@^0.9.2": version "0.9.5" resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.9.5.tgz#2cbaf9a89460da24b5ca6531b8bbfc23e1df50c7" @@ -1199,215 +841,6 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== -"@istanbuljs/load-nyc-config@^1.0.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" - integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== - dependencies: - camelcase "^5.3.1" - find-up "^4.1.0" - get-package-type "^0.1.0" - js-yaml "^3.13.1" - resolve-from "^5.0.0" - -"@istanbuljs/schema@^0.1.2": - version "0.1.3" - resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" - integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== - -"@jest/console@^28.1.1": - version "28.1.1" - resolved "https://registry.yarnpkg.com/@jest/console/-/console-28.1.1.tgz#305f8ca50b6e70413839f54c0e002b60a0f2fd7d" - integrity sha512-0RiUocPVFEm3WRMOStIHbRWllG6iW6E3/gUPnf4lkrVFyXIIDeCe+vlKeYyFOMhB2EPE6FLFCNADSOOQMaqvyA== - dependencies: - "@jest/types" "^28.1.1" - "@types/node" "*" - chalk "^4.0.0" - jest-message-util "^28.1.1" - jest-util "^28.1.1" - slash "^3.0.0" - -"@jest/core@^28.1.1": - version "28.1.1" - resolved "https://registry.yarnpkg.com/@jest/core/-/core-28.1.1.tgz#086830bec6267accf9af5ca76f794858e9f9f092" - integrity sha512-3pYsBoZZ42tXMdlcFeCc/0j9kOlK7MYuXs2B1QbvDgMoW1K9NJ4G/VYvIbMb26iqlkTfPHo7SC2JgjDOk/mxXw== - dependencies: - "@jest/console" "^28.1.1" - "@jest/reporters" "^28.1.1" - "@jest/test-result" "^28.1.1" - "@jest/transform" "^28.1.1" - "@jest/types" "^28.1.1" - "@types/node" "*" - ansi-escapes "^4.2.1" - chalk "^4.0.0" - ci-info "^3.2.0" - exit "^0.1.2" - graceful-fs "^4.2.9" - jest-changed-files "^28.0.2" - jest-config "^28.1.1" - jest-haste-map "^28.1.1" - jest-message-util "^28.1.1" - jest-regex-util "^28.0.2" - jest-resolve "^28.1.1" - jest-resolve-dependencies "^28.1.1" - jest-runner "^28.1.1" - jest-runtime "^28.1.1" - jest-snapshot "^28.1.1" - jest-util "^28.1.1" - jest-validate "^28.1.1" - jest-watcher "^28.1.1" - micromatch "^4.0.4" - pretty-format "^28.1.1" - rimraf "^3.0.0" - slash "^3.0.0" - strip-ansi "^6.0.0" - -"@jest/environment@^28.1.1": - version "28.1.1" - resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-28.1.1.tgz#c4cbf85283278d768f816ebd1a258ea6f9e39d4f" - integrity sha512-9auVQ2GzQ7nrU+lAr8KyY838YahElTX9HVjbQPPS2XjlxQ+na18G113OoBhyBGBtD6ZnO/SrUy5WR8EzOj1/Uw== - dependencies: - "@jest/fake-timers" "^28.1.1" - "@jest/types" "^28.1.1" - "@types/node" "*" - jest-mock "^28.1.1" - -"@jest/expect-utils@^28.1.1": - version "28.1.1" - resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-28.1.1.tgz#d84c346025b9f6f3886d02c48a6177e2b0360587" - integrity sha512-n/ghlvdhCdMI/hTcnn4qV57kQuV9OTsZzH1TTCVARANKhl6hXJqLKUkwX69ftMGpsbpt96SsDD8n8LD2d9+FRw== - dependencies: - jest-get-type "^28.0.2" - -"@jest/expect@^28.1.1": - version "28.1.1" - resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-28.1.1.tgz#ea4fcc8504b45835029221c0dc357c622a761326" - integrity sha512-/+tQprrFoT6lfkMj4mW/mUIfAmmk/+iQPmg7mLDIFOf2lyf7EBHaS+x3RbeR0VZVMe55IvX7QRoT/2aK3AuUXg== - dependencies: - expect "^28.1.1" - jest-snapshot "^28.1.1" - -"@jest/fake-timers@^28.1.1": - version "28.1.1" - resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-28.1.1.tgz#47ce33296ab9d680c76076d51ddbe65ceb3337f1" - integrity sha512-BY/3+TyLs5+q87rGWrGUY5f8e8uC3LsVHS9Diz8+FV3ARXL4sNnkLlIB8dvDvRrp+LUCGM+DLqlsYubizGUjIA== - dependencies: - "@jest/types" "^28.1.1" - "@sinonjs/fake-timers" "^9.1.1" - "@types/node" "*" - jest-message-util "^28.1.1" - jest-mock "^28.1.1" - jest-util "^28.1.1" - -"@jest/globals@^28.1.1": - version "28.1.1" - resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-28.1.1.tgz#c0a7977f85e26279cc090d9adcdf82b8a34c4061" - integrity sha512-dEgl/6v7ToB4vXItdvcltJBgny0xBE6xy6IYQrPJAJggdEinGxCDMivNv7sFzPcTITGquXD6UJwYxfJ/5ZwDSg== - dependencies: - "@jest/environment" "^28.1.1" - "@jest/expect" "^28.1.1" - "@jest/types" "^28.1.1" - -"@jest/reporters@^28.1.1": - version "28.1.1" - resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-28.1.1.tgz#9389f4bb3cce4d9b586f6195f83c79cd2a1c8662" - integrity sha512-597Zj4D4d88sZrzM4atEGLuO7SdA/YrOv9SRXHXRNC+/FwPCWxZhBAEzhXoiJzfRwn8zes/EjS8Lo6DouGN5Gg== - dependencies: - "@bcoe/v8-coverage" "^0.2.3" - "@jest/console" "^28.1.1" - "@jest/test-result" "^28.1.1" - "@jest/transform" "^28.1.1" - "@jest/types" "^28.1.1" - "@jridgewell/trace-mapping" "^0.3.7" - "@types/node" "*" - chalk "^4.0.0" - collect-v8-coverage "^1.0.0" - exit "^0.1.2" - glob "^7.1.3" - graceful-fs "^4.2.9" - istanbul-lib-coverage "^3.0.0" - istanbul-lib-instrument "^5.1.0" - istanbul-lib-report "^3.0.0" - istanbul-lib-source-maps "^4.0.0" - istanbul-reports "^3.1.3" - jest-message-util "^28.1.1" - jest-util "^28.1.1" - jest-worker "^28.1.1" - slash "^3.0.0" - string-length "^4.0.1" - strip-ansi "^6.0.0" - terminal-link "^2.0.0" - v8-to-istanbul "^9.0.0" - -"@jest/schemas@^28.0.2": - version "28.0.2" - resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-28.0.2.tgz#08c30df6a8d07eafea0aef9fb222c5e26d72e613" - integrity sha512-YVDJZjd4izeTDkij00vHHAymNXQ6WWsdChFRK86qck6Jpr3DCL5W3Is3vslviRlP+bLuMYRLbdp98amMvqudhA== - dependencies: - "@sinclair/typebox" "^0.23.3" - -"@jest/source-map@^28.0.2": - version "28.0.2" - resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-28.0.2.tgz#914546f4410b67b1d42c262a1da7e0406b52dc90" - integrity sha512-Y9dxC8ZpN3kImkk0LkK5XCEneYMAXlZ8m5bflmSL5vrwyeUpJfentacCUg6fOb8NOpOO7hz2+l37MV77T6BFPw== - dependencies: - "@jridgewell/trace-mapping" "^0.3.7" - callsites "^3.0.0" - graceful-fs "^4.2.9" - -"@jest/test-result@^28.1.1": - version "28.1.1" - resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-28.1.1.tgz#c6f18d1bbb01aa88925dd687872a75f8414b317a" - integrity sha512-hPmkugBktqL6rRzwWAtp1JtYT4VHwv8OQ+9lE5Gymj6dHzubI/oJHMUpPOt8NrdVWSrz9S7bHjJUmv2ggFoUNQ== - dependencies: - "@jest/console" "^28.1.1" - "@jest/types" "^28.1.1" - "@types/istanbul-lib-coverage" "^2.0.0" - collect-v8-coverage "^1.0.0" - -"@jest/test-sequencer@^28.1.1": - version "28.1.1" - resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-28.1.1.tgz#f594ee2331df75000afe0d1ae3237630ecec732e" - integrity sha512-nuL+dNSVMcWB7OOtgb0EGH5AjO4UBCt68SLP08rwmC+iRhyuJWS9MtZ/MpipxFwKAlHFftbMsydXqWre8B0+XA== - dependencies: - "@jest/test-result" "^28.1.1" - graceful-fs "^4.2.9" - jest-haste-map "^28.1.1" - slash "^3.0.0" - -"@jest/transform@^28.1.1": - version "28.1.1" - resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-28.1.1.tgz#83541f2a3f612077c8501f49cc4e205d4e4a6b27" - integrity sha512-PkfaTUuvjUarl1EDr5ZQcCA++oXkFCP9QFUkG0yVKVmNObjhrqDy0kbMpMebfHWm3CCDHjYNem9eUSH8suVNHQ== - dependencies: - "@babel/core" "^7.11.6" - "@jest/types" "^28.1.1" - "@jridgewell/trace-mapping" "^0.3.7" - babel-plugin-istanbul "^6.1.1" - chalk "^4.0.0" - convert-source-map "^1.4.0" - fast-json-stable-stringify "^2.0.0" - graceful-fs "^4.2.9" - jest-haste-map "^28.1.1" - jest-regex-util "^28.0.2" - jest-util "^28.1.1" - micromatch "^4.0.4" - pirates "^4.0.4" - slash "^3.0.0" - write-file-atomic "^4.0.1" - -"@jest/types@^28.1.1": - version "28.1.1" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-28.1.1.tgz#d059bbc80e6da6eda9f081f293299348bd78ee0b" - integrity sha512-vRXVqSg1VhDnB8bWcmvLzmg0Bt9CRKVgHPXqYwvWMX3TvAjeO+nRuK6+VdTKCtWOvYlmkF/HqNAL/z+N3B53Kw== - dependencies: - "@jest/schemas" "^28.0.2" - "@types/istanbul-lib-coverage" "^2.0.0" - "@types/istanbul-reports" "^3.0.0" - "@types/node" "*" - "@types/yargs" "^17.0.8" - chalk "^4.0.0" - "@jridgewell/gen-mapping@^0.1.0": version "0.1.1" resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz#e5d2e450306a9491e3bd77e323e38d7aff315996" @@ -1425,6 +858,15 @@ "@jridgewell/sourcemap-codec" "^1.4.10" "@jridgewell/trace-mapping" "^0.3.9" +"@jridgewell/gen-mapping@^0.3.2": + version "0.3.2" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz#c1aedc61e853f2bb9f5dfe6d4442d3b565b253b9" + integrity sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A== + dependencies: + "@jridgewell/set-array" "^1.0.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.9" + "@jridgewell/resolve-uri@^3.0.3": version "3.0.7" resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.0.7.tgz#30cd49820a962aff48c8fffc5cd760151fca61fe" @@ -1435,20 +877,17 @@ resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.1.tgz#36a6acc93987adcf0ba50c66908bd0b70de8afea" integrity sha512-Ct5MqZkLGEXTVmQYbGtx9SVqD2fqwvdubdps5D3djjAkgkKwT918VNOz65pEHFaYTeWcukmJmH5SwsA9Tn2ObQ== -"@jridgewell/source-map@^0.3.2": - version "0.3.2" - resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.2.tgz#f45351aaed4527a298512ec72f81040c998580fb" - integrity sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw== - dependencies: - "@jridgewell/gen-mapping" "^0.3.0" - "@jridgewell/trace-mapping" "^0.3.9" +"@jridgewell/set-array@^1.0.1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" + integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== "@jridgewell/sourcemap-codec@^1.4.10": version "1.4.13" resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.13.tgz#b6461fb0c2964356c469e115f504c95ad97ab88c" integrity sha512-GryiOJmNcWbovBxTfZSF71V/mXbgcV3MewDe3kIMCLyIh5e7SKAeUZs+rMnJ8jkMolZ/4/VsdBmMrw3l+VdZ3w== -"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.7", "@jridgewell/trace-mapping@^0.3.9": +"@jridgewell/trace-mapping@^0.3.9": version "0.3.14" resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz#b231a081d8f66796e475ad588a1ef473112701ed" integrity sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ== @@ -1456,6 +895,175 @@ "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" +"@lezer/common@^1.0.0": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@lezer/common/-/common-1.0.1.tgz#d014fda6d582c24336fadf2715e76f02f73c8908" + integrity sha512-8TR5++Q/F//tpDsLd5zkrvEX5xxeemafEaek7mUp7Y+bI8cKQXdSqhzTOBaOogETcMOVr0pT3BBPXp13477ciw== + +"@lezer/cpp@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@lezer/cpp/-/cpp-1.0.0.tgz#3293fd88aaf16a6d4f18188602b4d931be8f0915" + integrity sha512-Klk3/AIEKoptmm6cNm7xTulNXjdTKkD+hVOEcz/NeRg8tIestP5hsGHJeFDR/XtyDTxsjoPjKZRIGohht7zbKw== + dependencies: + "@lezer/highlight" "^1.0.0" + "@lezer/lr" "^1.0.0" + +"@lezer/css@^1.0.0": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@lezer/css/-/css-1.0.1.tgz#589d16d557024481f38dd8a036be3c3db1a199c2" + integrity sha512-kLGsbzXdp1ntzO2jDwFf+2w76EBlLiD4FKofx7tgkdqeFRoslFiMS2qqbNtAauXw8ihZ4cE5YpxSpfsKXSs5Sg== + dependencies: + "@lezer/highlight" "^1.0.0" + "@lezer/lr" "^1.0.0" + +"@lezer/highlight@1.1.2", "@lezer/highlight@^1.0.0": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@lezer/highlight/-/highlight-1.1.2.tgz#60cd6c2a0a2cf753b8a026b04feeb0ea8df326ea" + integrity sha512-CAun1WR1glxG9ZdOokTZwXbcwB7PXkIEyZRUMFBVwSrhTcogWq634/ByNImrkUnQhjju6xsIaOBIxvcRJtplXQ== + dependencies: + "@lezer/common" "^1.0.0" + +"@lezer/html@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@lezer/html/-/html-1.0.1.tgz#5d62b98cdd37e50394e1b7097c86d8ce3ef128b8" + integrity sha512-sC00zEt3GBh3vVO6QaGX4YZCl41S9dHWN/WGBsDixy9G+sqOC7gsa4cxA/fmRVAiBvhqYkJk+5Ul4oul92CPVw== + dependencies: + "@lezer/common" "^1.0.0" + "@lezer/highlight" "^1.0.0" + "@lezer/lr" "^1.0.0" + +"@lezer/java@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@lezer/java/-/java-1.0.0.tgz#fe74e062350f7a4268107e7562971bfbad994f49" + integrity sha512-z2EA0JHq2WoiKfQy5uOOd4t17PJtq8guh58gPkSzOnNcQ7DNbkrU+Axak+jL8+Noinwyz2tRNOseQFj+Tg+P0A== + dependencies: + "@lezer/highlight" "^1.0.0" + "@lezer/lr" "^1.0.0" + +"@lezer/javascript@^1.0.0": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@lezer/javascript/-/javascript-1.0.2.tgz#79b5c2c77e27322a0ae516395a193574b9ad3f5e" + integrity sha512-IjOVeIRhM8IuafWNnk+UzRz7p4/JSOKBNINLYLsdSGuJS9Ju7vFdc82AlTt0jgtV5D8eBZf4g0vK4d3ttBNz7A== + dependencies: + "@lezer/highlight" "^1.0.0" + "@lezer/lr" "^1.0.0" + +"@lezer/json@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@lezer/json/-/json-1.0.0.tgz#848ad9c2c3e812518eb02897edd5a7f649e9c160" + integrity sha512-zbAuUY09RBzCoCA3lJ1+ypKw5WSNvLqGMtasdW6HvVOqZoCpPr8eWrsGnOVWGKGn8Rh21FnrKRVlJXrGAVUqRw== + dependencies: + "@lezer/highlight" "^1.0.0" + "@lezer/lr" "^1.0.0" + +"@lezer/lezer@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@lezer/lezer/-/lezer-1.1.0.tgz#6a42b969735e632dd7ae6a102cdc7e12e474f86c" + integrity sha512-XTomM3C2MzHNuZwjYbyYZ44IRV6rHIOvi++yAD1O4djlDoKAnikx3BFoREK2g/z8zUIc/kyWuZO9W9xN4/OR1g== + dependencies: + "@lezer/highlight" "^1.0.0" + "@lezer/lr" "^1.0.0" + +"@lezer/lr@^1.0.0": + version "1.2.3" + resolved "https://registry.yarnpkg.com/@lezer/lr/-/lr-1.2.3.tgz#f44ca844f15f6762fde4eab877d110567e34ffa1" + integrity sha512-qpB7rBzH8f6Mzjv2AVZRahcm+2Cf7nbIH++uXbvVOL1yIRvVWQ3HAM/saeBLCyz/togB7LGo76qdJYL1uKQlqA== + dependencies: + "@lezer/common" "^1.0.0" + +"@lezer/markdown@^1.0.0": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@lezer/markdown/-/markdown-1.0.2.tgz#8c804a9f6fe1ccca4a20acd2fd9fbe0fae1ae178" + integrity sha512-8CY0OoZ6V5EzPjSPeJ4KLVbtXdLBd8V6sRCooN5kHnO28ytreEGTyrtU/zUwo/XLRzGr/e1g44KlzKi3yWGB5A== + dependencies: + "@lezer/common" "^1.0.0" + "@lezer/highlight" "^1.0.0" + +"@lezer/php@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@lezer/php/-/php-1.0.0.tgz#522d2d2d8a4eee6c598060e2a222526953c66adb" + integrity sha512-kFQu/mk/vmjpA+fjQU87d9eimqKJ9PFCa8CZCPFWGEwNnm7Ahpw32N+HYEU/YAQ0XcfmOAnW/YJCEa8WpUOMMw== + dependencies: + "@lezer/highlight" "^1.0.0" + "@lezer/lr" "^1.0.0" + +"@lezer/python@^1.0.0": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@lezer/python/-/python-1.1.1.tgz#6d688071ed93d063a589a7d31df3279b1eba607a" + integrity sha512-ArUGh9kvdaOVu6IkSaYUS9WFQeMAFVWKRuZo6vexnxoeCLnxf0Y9DCFEAMMa7W9SQBGYE55OarSpPqSkdOXSCA== + dependencies: + "@lezer/highlight" "^1.0.0" + "@lezer/lr" "^1.0.0" + +"@lezer/rust@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@lezer/rust/-/rust-1.0.0.tgz#939f3e7b0376ebe13f4ac336ed7d59ca2c8adf52" + integrity sha512-IpGAxIjNxYmX9ra6GfQTSPegdCAWNeq23WNmrsMMQI7YNSvKtYxO4TX5rgZUmbhEucWn0KTBMeDEPXg99YKtTA== + dependencies: + "@lezer/highlight" "^1.0.0" + "@lezer/lr" "^1.0.0" + +"@lezer/xml@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@lezer/xml/-/xml-1.0.0.tgz#02817a3d421e7189b50fd31ed17430b2e1c8c0d8" + integrity sha512-73iI9UK8iqSvWtLlOEl/g+50ivwQn8Ge6foHVN66AXUS1RccFnAoc7BYU8b3c8/rP6dfCOGqAGaWLxBzhj60MA== + dependencies: + "@lezer/highlight" "^1.0.0" + "@lezer/lr" "^1.0.0" + +"@motionone/animation@^10.13.1": + version "10.14.0" + resolved "https://registry.yarnpkg.com/@motionone/animation/-/animation-10.14.0.tgz#2f2a3517183bb58d82e389aac777fe0850079de6" + integrity sha512-h+1sdyBP8vbxEBW5gPFDnj+m2DCqdlAuf2g6Iafb1lcMnqjsRXWlPw1AXgvUMXmreyhqmPbJqoNfIKdytampRQ== + dependencies: + "@motionone/easing" "^10.14.0" + "@motionone/types" "^10.14.0" + "@motionone/utils" "^10.14.0" + tslib "^2.3.1" + +"@motionone/dom@10.13.1": + version "10.13.1" + resolved "https://registry.yarnpkg.com/@motionone/dom/-/dom-10.13.1.tgz#fc29ea5d12538f21b211b3168e502cfc07a24882" + integrity sha512-zjfX+AGMIt/fIqd/SL1Lj93S6AiJsEA3oc5M9VkUr+Gz+juRmYN1vfvZd6MvEkSqEjwPQgcjN7rGZHrDB9APfQ== + dependencies: + "@motionone/animation" "^10.13.1" + "@motionone/generators" "^10.13.1" + "@motionone/types" "^10.13.0" + "@motionone/utils" "^10.13.1" + hey-listen "^1.0.8" + tslib "^2.3.1" + +"@motionone/easing@^10.14.0": + version "10.14.0" + resolved "https://registry.yarnpkg.com/@motionone/easing/-/easing-10.14.0.tgz#d8154b7f71491414f3cdee23bd3838d763fffd00" + integrity sha512-2vUBdH9uWTlRbuErhcsMmt1jvMTTqvGmn9fHq8FleFDXBlHFs5jZzHJT9iw+4kR1h6a4SZQuCf72b9ji92qNYA== + dependencies: + "@motionone/utils" "^10.14.0" + tslib "^2.3.1" + +"@motionone/generators@^10.13.1": + version "10.14.0" + resolved "https://registry.yarnpkg.com/@motionone/generators/-/generators-10.14.0.tgz#e05d9dd56da78a4b92db99185848a0f3db62242d" + integrity sha512-6kRHezoFfIjFN7pPpaxmkdZXD36tQNcyJe3nwVqwJ+ZfC0e3rFmszR8kp9DEVFs9QL/akWjuGPSLBI1tvz+Vjg== + dependencies: + "@motionone/types" "^10.14.0" + "@motionone/utils" "^10.14.0" + tslib "^2.3.1" + +"@motionone/types@^10.13.0", "@motionone/types@^10.14.0": + version "10.14.0" + resolved "https://registry.yarnpkg.com/@motionone/types/-/types-10.14.0.tgz#148c34f3270b175397e49c3058b33fab405c21e3" + integrity sha512-3bNWyYBHtVd27KncnJLhksMFQ5o2MSdk1cA/IZqsHtA9DnRM1SYgN01CTcJ8Iw8pCXF5Ocp34tyAjY7WRpOJJQ== + +"@motionone/utils@^10.13.1", "@motionone/utils@^10.14.0": + version "10.14.0" + resolved "https://registry.yarnpkg.com/@motionone/utils/-/utils-10.14.0.tgz#a19a3464ed35b08506747b062d035c7bc9bbe708" + integrity sha512-sLWBLPzRqkxmOTRzSaD3LFQXCPHvDzyHJ1a3VP9PRzBxyVd2pv51/gMOsdAcxQ9n+MIeGJnxzXBYplUHKj4jkw== + dependencies: + "@motionone/types" "^10.14.0" + hey-listen "^1.0.8" + tslib "^2.3.1" + "@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" @@ -1477,70 +1085,39 @@ "@nodelib/fs.scandir" "2.1.4" fastq "^1.6.0" -"@npmcli/fs@^1.0.0": - version "1.1.1" - resolved "https://registry.yarnpkg.com/@npmcli/fs/-/fs-1.1.1.tgz#72f719fe935e687c56a4faecf3c03d06ba593257" - integrity sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ== - dependencies: - "@gar/promisify" "^1.0.1" - semver "^7.3.5" - -"@npmcli/move-file@^1.0.1": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@npmcli/move-file/-/move-file-1.1.2.tgz#1a82c3e372f7cae9253eb66d72543d6b8685c674" - integrity sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg== - dependencies: - mkdirp "^1.0.4" - rimraf "^3.0.2" - -"@preact/signals-core@^1.2.2": +"@preact/signals-core@^1.2.1": version "1.2.2" resolved "https://registry.yarnpkg.com/@preact/signals-core/-/signals-core-1.2.2.tgz#279dcc5ab249de2f2e8f6e6779b1958256ba843e" integrity sha512-z3/bCj7rRA21RJb4FeJ4guCrD1CQbaURHkCTunUWQpxUMAFOPXCD8tSFqERyGrrcSb4T3Hrmdc1OAl0LXBHwiw== -"@preact/signals-react@^1.2.1": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@preact/signals-react/-/signals-react-1.2.1.tgz#6d5d305ebdb38c879043acebc65c0d9351e663c1" - integrity sha512-73J8sL1Eru7Ot4yBYOCPj1izEZjzCEXlembRgk6C7PkwsqoAVbCxMlDOFfCLoPFuJ6qeGatrJzRkcycXppMqVQ== - dependencies: - "@preact/signals-core" "^1.2.2" - use-sync-external-store "^1.2.0" - -"@sinclair/typebox@^0.23.3": - version "0.23.5" - resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.23.5.tgz#93f7b9f4e3285a7a9ade7557d9a8d36809cbc47d" - integrity sha512-AFBVi/iT4g20DHoujvMH1aEDn8fGJh4xsRGCP6d8RpLPMqsNPvW01Jcn0QysXTsg++/xj25NmJsGyH9xug/wKg== - -"@sinonjs/commons@^1.7.0": - version "1.8.3" - resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.3.tgz#3802ddd21a50a949b6721ddd72da36e67e7f1b2d" - integrity sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ== +"@preact/signals-react@1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@preact/signals-react/-/signals-react-1.1.1.tgz#8c77e86bc94dcd7f58143e06b6d7564ee79e9585" + integrity sha512-U5HNWBt4q5pmsZjDOuVcz3OXLQtaBMMSErnTHFohOFQClBqHlVD/hmhayEEO38I9iU71kofhw2ngeWso/GsVMw== dependencies: - type-detect "4.0.8" + "@preact/signals-core" "^1.2.1" -"@sinonjs/fake-timers@^9.1.1": - version "9.1.2" - resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz#4eaab737fab77332ab132d396a3c0d364bd0ea8c" - integrity sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw== - dependencies: - "@sinonjs/commons" "^1.7.0" +"@remix-run/router@1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.0.2.tgz#1c17eadb2fa77f80a796ad5ea9bf108e6993ef06" + integrity sha512-GRSOFhJzjGN+d4sKHTMSvNeUPoZiDHWmRnXfzaxrqe7dE/Nzlc8BiMSJdLDESZlndM7jIUrZ/F4yWqVYlI0rwQ== -"@tailwindcss/forms@^0.5.2": - version "0.5.2" - resolved "https://registry.yarnpkg.com/@tailwindcss/forms/-/forms-0.5.2.tgz#4ef45f9916dcb37838cbe7fecdcc4ba7a7c2ab59" - integrity sha512-pSrFeJB6Bg1Mrg9CdQW3+hqZXAKsBrSG9MAfFLKy1pVA4Mb4W7C0k7mEhlmS2Dfo/otxrQOET7NJiJ9RrS563w== +"@tailwindcss/forms@0.5.3": + version "0.5.3" + resolved "https://registry.yarnpkg.com/@tailwindcss/forms/-/forms-0.5.3.tgz#e4d7989686cbcaf416c53f1523df5225332a86e7" + integrity sha512-y5mb86JUoiUgBjY/o6FJSFZSEttfb3Q5gllE4xoKjAAD+vBrnIhE4dViwUuow3va8mpH4s9jyUbUbrRGoRdc2Q== dependencies: mini-svg-data-uri "^1.2.3" -"@tailwindcss/line-clamp@^0.4.0": - version "0.4.0" - resolved "https://registry.yarnpkg.com/@tailwindcss/line-clamp/-/line-clamp-0.4.0.tgz#03353e31e77636b785f2336e8c978502cec1de81" - integrity sha512-HQZo6gfx1D0+DU3nWlNLD5iA6Ef4JAXh0LeD8lOGrJwEDBwwJNKQza6WoXhhY1uQrxOuU8ROxV7CqiQV4CoiLw== +"@tailwindcss/line-clamp@0.4.2": + version "0.4.2" + resolved "https://registry.yarnpkg.com/@tailwindcss/line-clamp/-/line-clamp-0.4.2.tgz#f353c5a8ab2c939c6267ac5b907f012e5ee130f9" + integrity sha512-HFzAQuqYCjyy/SX9sLGB1lroPzmcnWv1FHkIpmypte10hptf4oPUfucryMKovZh2u0uiS9U5Ty3GghWfEJGwVw== -"@testing-library/dom@^8.0.0", "@testing-library/dom@^8.11.1", "@testing-library/dom@^8.14.0": - version "8.14.0" - resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-8.14.0.tgz#c9830a21006d87b9ef6e1aae306cf49b0283e28e" - integrity sha512-m8FOdUo77iMTwVRCyzWcqxlEIk+GnopbrRI15a0EaLbpZSCinIVI4kSQzWhkShK83GogvEFJSsHF3Ws0z1vrqA== +"@testing-library/dom@8.19.0", "@testing-library/dom@^8.5.0": + version "8.19.0" + resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-8.19.0.tgz#bd3f83c217ebac16694329e413d9ad5fdcfd785f" + integrity sha512-6YWYPPpxG3e/xOo6HIWwB/58HukkwIVTOaZ0VwdMVjhRUX/01E4FtQbck9GazOOj7MXHc5RBzMrU86iBJHbI+A== dependencies: "@babel/code-frame" "^7.10.4" "@babel/runtime" "^7.12.5" @@ -1551,235 +1128,132 @@ lz-string "^1.4.4" pretty-format "^27.0.2" -"@testing-library/jest-dom@^5.16.4": - version "5.16.4" - resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-5.16.4.tgz#938302d7b8b483963a3ae821f1c0808f872245cd" - integrity sha512-Gy+IoFutbMQcky0k+bqqumXZ1cTGswLsFqmNLzNdSKkU9KGV2u9oXhukCbbJ9/LRPKiqwxEE8VpV/+YZlfkPUA== - dependencies: - "@babel/runtime" "^7.9.2" - "@types/testing-library__jest-dom" "^5.9.1" - aria-query "^5.0.0" - chalk "^3.0.0" - css "^3.0.0" - css.escape "^1.5.1" - dom-accessibility-api "^0.5.6" - lodash "^4.17.15" - redent "^3.0.0" - -"@testing-library/react@12.1.5": - version "12.1.5" - resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-12.1.5.tgz#bb248f72f02a5ac9d949dea07279095fa577963b" - integrity sha512-OfTXCJUFgjd/digLUuPxa0+/3ZxsQmE7ub9kcbW/wi96Bh3o/p5vrETcBGfP17NWPGqeYYl5LTRpwyGoMC4ysg== +"@testing-library/react@13.4.0": + version "13.4.0" + resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-13.4.0.tgz#6a31e3bf5951615593ad984e96b9e5e2d9380966" + integrity sha512-sXOGON+WNTh3MLE9rve97ftaZukN3oNf2KjDy7YTx6hcTO2uuLHuCGynMDhFwGw/jYf4OJ2Qk0i4i79qMNNkyw== dependencies: "@babel/runtime" "^7.12.5" - "@testing-library/dom" "^8.0.0" - "@types/react-dom" "<18.0.0" + "@testing-library/dom" "^8.5.0" + "@types/react-dom" "^18.0.0" -"@testing-library/user-event@^14.2.1": - version "14.2.1" - resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-14.2.1.tgz#8c5ff2d004544bb2220e1d864f7267fe7eb6c556" - integrity sha512-HOr1QiODrq+0j9lKU5i10y9TbhxMBMRMGimNx10asdmau9cb8Xb1Vyg0GvTwyIL2ziQyh2kAloOtAQFBQVuecA== +"@testing-library/user-event@14.4.3": + version "14.4.3" + resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-14.4.3.tgz#af975e367743fa91989cd666666aec31a8f50591" + integrity sha512-kCUc5MEwaEMakkO5x7aoD+DLi02ehmEM2QCGWvNqAS1dV/fAvORWEjnjsEIvml59M7Y5kCkWN6fCCyPOe8OL6Q== "@types/aria-query@^4.2.0": version "4.2.2" resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-4.2.2.tgz#ed4e0ad92306a704f9fb132a0cfcf77486dbe2bc" integrity sha512-HnYpAE1Y6kRyKM/XkEuiRQhTHvkzMBurTHnpFLYLBGPIylZNPs9jJcuOOYWxPLJCSEtmZT0Y8rHDokKN7rRTig== -"@types/babel__core@^7.1.14": - version "7.1.19" - resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.19.tgz#7b497495b7d1b4812bdb9d02804d0576f43ee460" - integrity sha512-WEOTgRsbYkvA/KCsDwVEGkd7WAr1e3g31VHQ8zy5gul/V1qKullU/BU5I68X5v7V3GnB9eotmom4v5a5gjxorw== - dependencies: - "@babel/parser" "^7.1.0" - "@babel/types" "^7.0.0" - "@types/babel__generator" "*" - "@types/babel__template" "*" - "@types/babel__traverse" "*" - -"@types/babel__generator@*": - version "7.6.4" - resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.4.tgz#1f20ce4c5b1990b37900b63f050182d28c2439b7" - integrity sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg== +"@types/chai-subset@^1.3.3": + version "1.3.3" + resolved "https://registry.yarnpkg.com/@types/chai-subset/-/chai-subset-1.3.3.tgz#97893814e92abd2c534de422cb377e0e0bdaac94" + integrity sha512-frBecisrNGz+F4T6bcc+NLeolfiojh5FxW2klu669+8BARtyQv2C/GkNW6FUodVe4BroGMP/wER/YDGc7rEllw== dependencies: - "@babel/types" "^7.0.0" + "@types/chai" "*" -"@types/babel__template@*": - version "7.4.1" - resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.1.tgz#3d1a48fd9d6c0edfd56f2ff578daed48f36c8969" - integrity sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g== - dependencies: - "@babel/parser" "^7.1.0" - "@babel/types" "^7.0.0" +"@types/chai@*", "@types/chai@^4.3.3": + version "4.3.3" + resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.3.tgz#3c90752792660c4b562ad73b3fbd68bf3bc7ae07" + integrity sha512-hC7OMnszpxhZPduX+m+nrx+uFoLkWOMiR4oa/AZF3MuSETYTZmFfJAHqZEM8MVlvfG7BEUcgvtwoCTxBp6hm3g== -"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": - version "7.17.1" - resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.17.1.tgz#1a0e73e8c28c7e832656db372b779bfd2ef37314" - integrity sha512-kVzjari1s2YVi77D3w1yuvohV2idweYXMCDzqBiVNN63TcDWrIlTVOYpqVrvbbyOE/IyzBoTKF0fdnLPEORFxA== +"@types/codemirror@0.0.109": + version "0.0.109" + resolved "https://registry.yarnpkg.com/@types/codemirror/-/codemirror-0.0.109.tgz#89d575ff1c7b462c4c3b8654f8bb38e5622e9036" + integrity sha512-cSdiHeeLjvGn649lRTNeYrVCDOgDrtP+bDDSFDd1TF+i0jKGPDRozno2NOJ9lTniso+taiv4kiVS8dgM8Jm5lg== dependencies: - "@babel/types" "^7.3.0" + "@types/tern" "*" -"@types/codemirror@^0.0.98": - version "0.0.98" - resolved "https://registry.yarnpkg.com/@types/codemirror/-/codemirror-0.0.98.tgz#b35c7a4ab1fc1684b08a4e3eb65240020556ebfb" - integrity sha512-cbty5LPayy2vNSeuUdjNA9tggG+go5vAxmnLDRWpiZI5a+RDBi9dlozy4/jW/7P/gletbBWbQREEa7A81YxstA== +"@types/concat-stream@^1.6.0": + version "1.6.1" + resolved "https://registry.yarnpkg.com/@types/concat-stream/-/concat-stream-1.6.1.tgz#24bcfc101ecf68e886aaedce60dfd74b632a1b74" + integrity sha512-eHE4cQPoj6ngxBZMvVf6Hw7Mh4jMW4U9lpGmS5GBPB9RYxlFg+CHaVN7ErNY4W9XfLIEn20b4VDYaIrbq0q4uA== dependencies: - "@types/tern" "*" + "@types/node" "*" -"@types/debounce@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@types/debounce/-/debounce-1.2.0.tgz#9ee99259f41018c640b3929e1bb32c3dcecdb192" - integrity sha512-bWG5wapaWgbss9E238T0R6bfo5Fh3OkeoSt245CM7JJwVwpw6MEBCbIxLq5z8KzsE3uJhzcIuQkyiZmzV3M/Dw== +"@types/debounce@1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@types/debounce/-/debounce-1.2.1.tgz#79b65710bc8b6d44094d286aecf38e44f9627852" + integrity sha512-epMsEE85fi4lfmJUH/89/iV/LI+F5CvNIvmgs5g5jYFPfhO2S/ae8WSsLOKWdwtoaZw9Q2IhJ4tQ5tFCcS/4HA== "@types/estree@*": version "0.0.45" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.45.tgz#e9387572998e5ecdac221950dab3e8c3b16af884" integrity sha512-jnqIUKDUqJbDIUxm0Uj7bnlMnRm1T/eZ9N+AVMqhPgzrba2GhGG5o/jCTwmdPK709nEZsGoMzXEDUjcXHa3W0g== -"@types/events@*", "@types/events@^3.0.0": +"@types/events@3.0.0": version "3.0.0" resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7" + integrity sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g== -"@types/glob@^7.1.1": - version "7.1.1" - resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.1.tgz#aa59a1c6e3fbc421e07ccd31a944c30eba521575" - dependencies: - "@types/events" "*" - "@types/minimatch" "*" - "@types/node" "*" - -"@types/graceful-fs@^4.1.3": - version "4.1.5" - resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.5.tgz#21ffba0d98da4350db64891f92a9e5db3cdb4e15" - integrity sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw== +"@types/form-data@0.0.33": + version "0.0.33" + resolved "https://registry.yarnpkg.com/@types/form-data/-/form-data-0.0.33.tgz#c9ac85b2a5fd18435b8c85d9ecb50e6d6c893ff8" + integrity sha512-8BSvG1kGm83cyJITQMZSulnl6QV8jqAGreJsc5tPu1Jq0vTSOiY/k24Wx82JRpWwZSqrala6sd5rWi6aNXvqcw== dependencies: "@types/node" "*" -"@types/history@*": - version "4.7.2" - resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.2.tgz#0e670ea254d559241b6eeb3894f8754991e73220" - -"@types/hoist-non-react-statics@*", "@types/hoist-non-react-statics@^3.3.0": +"@types/hoist-non-react-statics@*": version "3.3.1" resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f" dependencies: "@types/react" "*" hoist-non-react-statics "^3.3.0" -"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": - version "2.0.4" - resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz#8467d4b3c087805d63580480890791277ce35c44" - integrity sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g== - -"@types/istanbul-lib-report@*": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#c14c24f18ea8190c118ee7562b7ff99a36552686" - integrity sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg== - dependencies: - "@types/istanbul-lib-coverage" "*" - -"@types/istanbul-reports@^3.0.0": - version "3.0.1" - resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz#9153fe98bba2bd565a63add9436d6f0d7f8468ff" - integrity sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw== - dependencies: - "@types/istanbul-lib-report" "*" - -"@types/jest@*", "@types/jest@^28.1.3": - version "28.1.3" - resolved "https://registry.yarnpkg.com/@types/jest/-/jest-28.1.3.tgz#52f3f3e50ce59191ff5fbb1084896cc0cf30c9ce" - integrity sha512-Tsbjk8Y2hkBaY/gJsataeb4q9Mubw9EOz7+4RjPkzD5KjTvHHs7cpws22InaoXxAVAhF5HfFbzJjo6oKWqSZLw== - dependencies: - jest-matcher-utils "^28.0.0" - pretty-format "^28.0.0" - -"@types/json-schema@^7.0.4", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": +"@types/json-schema@^7.0.9": version "7.0.11" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== -"@types/minimatch@*": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" +"@types/lodash@^4.14.175": + version "4.14.186" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.186.tgz#862e5514dd7bd66ada6c70ee5fce844b06c8ee97" + integrity sha512-eHcVlLXP0c2FlMPm56ITode2AgLMSa6aJ05JTTbYbI+7EMkCEE5qk2E41d5g2lCVTqRe0GnnRFurmlCsDODrPw== -"@types/node@*", "@types/node@^14.11.10": +"@types/node@*": version "14.11.10" resolved "https://registry.yarnpkg.com/@types/node/-/node-14.11.10.tgz#8c102aba13bf5253f35146affbf8b26275069bef" integrity sha512-yV1nWZPlMFpoXyoknm4S56y2nlTAuFYaJuQtYRAOU7xA/FJ9RY0Xm7QOkaYMMmr8ESdHIuUb6oQgR/0+2NqlyA== +"@types/node@18.11.9": + version "18.11.9" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.9.tgz#02d013de7058cea16d36168ef2fc653464cfbad4" + integrity sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg== + +"@types/node@^10.0.3": + version "10.17.60" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.60.tgz#35f3d6213daed95da7f0f73e75bcc6980e90597b" + integrity sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw== + +"@types/node@^8.0.0": + version "8.10.66" + resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.66.tgz#dd035d409df322acc83dff62a602f12a5783bbb3" + integrity sha512-tktOkFUA4kXx2hhhrB8bIFb5TbwzS4uOhKEmwiD+NoiL0qtP2OQ9mFldbgD4dV1djrlBYP6eBuQZiWjuHUpqFw== + "@types/parse-json@^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== -"@types/prettier@^2.1.5": - version "2.6.3" - resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.6.3.tgz#68ada76827b0010d0db071f739314fa429943d0a" - integrity sha512-ymZk3LEC/fsut+/Q5qejp6R9O1rMxz3XaRHDV6kX8MrGAhOSPqVARbDi+EZvInBpw+BnCX3TD240byVkOfQsHg== - "@types/prop-types@*": version "15.7.1" resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.1.tgz#f1a11e7babb0c3cad68100be381d1e064c68f1f6" -"@types/qrcode.react@^1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@types/qrcode.react/-/qrcode.react-1.0.1.tgz#0904e7a075a6274a5258f19567b4f64013c159d8" - integrity sha512-PcVCjpsiT2KFKfJibOgTQtkt0QQT/6GbQUp1Np/hMPhwUzMJ2DRUkR9j7tXN9Q8X06qukw+RbaJ8lJ22SBod+Q== - dependencies: - "@types/react" "*" - -"@types/react-copy-to-clipboard@^4.3.0": - version "4.3.0" - resolved "https://registry.yarnpkg.com/@types/react-copy-to-clipboard/-/react-copy-to-clipboard-4.3.0.tgz#8e07becb4f11cfced4bd36038cb5bdf5c2658be5" - integrity sha512-iideNPRyroENqsOFh1i2Dv3zkviYS9r/9qD9Uh3Z9NNoAAqqa2x53i7iGndGNnJFIo20wIu7Hgh77tx1io8bgw== - dependencies: - "@types/react" "*" - -"@types/react-dom@<18.0.0": - version "17.0.17" - resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.17.tgz#2e3743277a793a96a99f1bf87614598289da68a1" - integrity sha512-VjnqEmqGnasQKV0CWLevqMTXBYG9GbwuE6x3VetERLh0cq2LTptFE73MrQi2S7GkKXCf2GgwItB/melLnxfnsg== - dependencies: - "@types/react" "^17" - -"@types/react-dom@^16.9.16": - version "16.9.16" - resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.9.16.tgz#c591f2ed1c6f32e9759dfa6eb4abfd8041f29e39" - integrity sha512-Oqc0RY4fggGA3ltEgyPLc3IV9T73IGoWjkONbsyJ3ZBn+UPPCYpU2ec0i3cEbJuEdZtkqcCF2l1zf2pBdgUGSg== - dependencies: - "@types/react" "^16" - -"@types/react-redux@^7.1.1": - version "7.1.1" - resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.1.tgz#eb01e89cf71cad77df9f442b819d5db692b997cb" - dependencies: - "@types/hoist-non-react-statics" "^3.3.0" - "@types/react" "*" - hoist-non-react-statics "^3.3.0" - redux "^4.0.0" +"@types/qs@^6.2.31": + version "6.9.7" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" + integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== -"@types/react-router-dom@^5.1.3": - version "5.1.3" - resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.1.3.tgz#b5d28e7850bd274d944c0fbbe5d57e6b30d71196" +"@types/react-dom@18.0.8", "@types/react-dom@^18.0.0": + version "18.0.8" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.0.8.tgz#d2606d855186cd42cc1b11e63a71c39525441685" + integrity sha512-C3GYO0HLaOkk9dDAz3Dl4sbe4AKUGTCfFIZsz3n/82dPNN8Du533HzKatDxeUYWu24wJgMP1xICqkWk1YOLOIw== dependencies: - "@types/history" "*" "@types/react" "*" - "@types/react-router" "*" -"@types/react-router@*", "@types/react-router@^5.1.3": - version "5.1.3" - resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.3.tgz#7c7ca717399af64d8733d8cb338dd43641b96f2d" - dependencies: - "@types/history" "*" - "@types/react" "*" - -"@types/react-transition-group@^4.4.0": - version "4.4.0" - resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.0.tgz#882839db465df1320e4753e6e9f70ca7e9b4d46d" - integrity sha512-/QfLHGpu+2fQOqQaXh8MG9q03bFENooTb/it4jr5kKaZlDQfWvjqWZg48AwzPVMBHlRuTRAY7hRHCEOXz5kV6w== - dependencies: - "@types/react" "*" - -"@types/react@*", "@types/react@^17": +"@types/react@*": version "17.0.47" resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.47.tgz#4ee71aaf4c5a9e290e03aa4d0d313c5d666b3b78" integrity sha512-mk0BL8zBinf2ozNr3qPnlu1oyVTYq+4V7WA76RgxUAtf0Em/Wbid38KN6n4abEkvO4xMTBWmnP1FtQzgkEiJoA== @@ -1788,10 +1262,10 @@ "@types/scheduler" "*" csstype "^3.0.2" -"@types/react@^16", "@types/react@^16.14.0": - version "16.14.26" - resolved "https://registry.yarnpkg.com/@types/react/-/react-16.14.26.tgz#82540a240ba7207ebe87d9579051bc19c9ef7605" - integrity sha512-c/5CYyciOO4XdFcNhZW1O2woVx86k4T+DO2RorHZL7EhitkNQgSD/SgpdZJAUJa/qjVgOmTM44gHkAdZSXeQuQ== +"@types/react@18.0.24": + version "18.0.24" + resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.24.tgz#2f79ed5b27f08d05107aab45c17919754cc44c20" + integrity sha512-wRJWT6ouziGUy+9uX0aW4YOJxAY0bG6/AOk5AW5QSvZqI7dk6VBIbXvcVgIw/W5Jrl24f77df98GEKTJGOLx7Q== dependencies: "@types/prop-types" "*" "@types/scheduler" "*" @@ -1802,15 +1276,15 @@ resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39" integrity sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew== -"@types/stack-utils@^2.0.0": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" - integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw== +"@types/semver@^7.3.12": + version "7.3.12" + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.12.tgz#920447fdd78d76b19de0438b7f60df3c4a80bf1c" + integrity sha512-WwA1MW0++RfXmCr12xeYOOC5baSC9mSb0ZqCquFzKhcoF4TvHu5MKOuXsncgZcpVFhB1pXd5hZmM0ryAoCp12A== -"@types/styled-components@^5.1.7": - version "5.1.7" - resolved "https://registry.yarnpkg.com/@types/styled-components/-/styled-components-5.1.7.tgz#3cd10b088c1cb1acde2e4b166b3e8275a3083710" - integrity sha512-BJzPhFygYspyefAGFZTZ/8lCEY4Tk+Iqktvnko3xmJf9LrLqs3+grxPeU3O0zLl6yjbYBopD0/VikbHgXDbJtA== +"@types/styled-components@5.1.26": + version "5.1.26" + resolved "https://registry.yarnpkg.com/@types/styled-components/-/styled-components-5.1.26.tgz#5627e6812ee96d755028a98dae61d28e57c233af" + integrity sha512-KuKJ9Z6xb93uJiIyxo/+ksS7yLjS1KzG6iv5i78dhVg/X3u5t1H7juRWqVmodIdz6wGVaIApo1u01kmFRdJHVw== dependencies: "@types/hoist-non-react-statics" "*" "@types/react" "*" @@ -1823,300 +1297,107 @@ dependencies: "@types/estree" "*" -"@types/testing-library__jest-dom@^5.9.1": - version "5.14.5" - resolved "https://registry.yarnpkg.com/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.5.tgz#d113709c90b3c75fdb127ec338dad7d5f86c974f" - integrity sha512-SBwbxYoyPIvxHbeHxTZX2Pe/74F/tX2/D3mMvzabdeJ25bBojfW0TyB8BHrbq/9zaaKICJZjLP+8r6AeZMFCuQ== - dependencies: - "@types/jest" "*" - -"@types/uuid@^3.4.5": - version "3.4.5" - resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-3.4.5.tgz#d4dc10785b497a1474eae0ba7f0cb09c0ddfd6eb" - dependencies: - "@types/node" "*" - -"@types/webpack-env@^1.15.2": - version "1.15.2" - resolved "https://registry.yarnpkg.com/@types/webpack-env/-/webpack-env-1.15.2.tgz#927997342bb9f4a5185a86e6579a0a18afc33b0a" - integrity sha512-67ZgZpAlhIICIdfQrB5fnDvaKFcDxpKibxznfYRVAT4mQE41Dido/3Ty+E3xGBmTogc5+0Qb8tWhna+5B8z1iQ== - -"@types/yargs-parser@*": - version "21.0.0" - resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.0.tgz#0c60e537fa790f5f9472ed2776c2b71ec117351b" - integrity sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA== - -"@types/yargs@^17.0.8": - version "17.0.10" - resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.10.tgz#591522fce85d8739bca7b8bb90d048e4478d186a" - integrity sha512-gmEaFwpj/7f/ROdtIlci1R1VYU1J4j95m8T+Tj3iBgiBFKg1foE/PSl93bBd5T9LDXNPo8UlNN6W0qwD8O5OaA== - dependencies: - "@types/yargs-parser" "*" - -"@types/yup@^0.29.3": - version "0.29.3" - resolved "https://registry.yarnpkg.com/@types/yup/-/yup-0.29.3.tgz#5a85024796bffe0eb01601bfc180fe218356dba4" - integrity sha512-XxZFKnxzTfm+DR8MMBA35UUXfUPmjPpi8HJ90VZg7q/LIbtiOhVGJ26gNnATcflcpnIyf2Qm9A+oEhswaqoDpA== - -"@typescript-eslint/eslint-plugin@^5.29.0": - version "5.29.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.29.0.tgz#c67794d2b0fd0b4a47f50266088acdc52a08aab6" - integrity sha512-kgTsISt9pM53yRFQmLZ4npj99yGl3x3Pl7z4eA66OuTzAGC4bQB5H5fuLwPnqTKU3yyrrg4MIhjF17UYnL4c0w== +"@typescript-eslint/eslint-plugin@5.41.0": + version "5.41.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.41.0.tgz#f8eeb1c6bb2549f795f3ba71aec3b38d1ab6b1e1" + integrity sha512-DXUS22Y57/LAFSg3x7Vi6RNAuLpTXwxB9S2nIA7msBb/Zt8p7XqMwdpdc1IU7CkOQUPgAqR5fWvxuKCbneKGmA== dependencies: - "@typescript-eslint/scope-manager" "5.29.0" - "@typescript-eslint/type-utils" "5.29.0" - "@typescript-eslint/utils" "5.29.0" + "@typescript-eslint/scope-manager" "5.41.0" + "@typescript-eslint/type-utils" "5.41.0" + "@typescript-eslint/utils" "5.41.0" debug "^4.3.4" - functional-red-black-tree "^1.0.1" ignore "^5.2.0" regexpp "^3.2.0" semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/parser@^5.29.0": - version "5.29.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.29.0.tgz#41314b195b34d44ff38220caa55f3f93cfca43cf" - integrity sha512-ruKWTv+x0OOxbzIw9nW5oWlUopvP/IQDjB5ZqmTglLIoDTctLlAJpAQFpNPJP/ZI7hTT9sARBosEfaKbcFuECw== +"@typescript-eslint/parser@5.41.0": + version "5.41.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.41.0.tgz#0414a6405007e463dc527b459af1f19430382d67" + integrity sha512-HQVfix4+RL5YRWZboMD1pUfFN8MpRH4laziWkkAzyO1fvNOY/uinZcvo3QiFJVS/siNHupV8E5+xSwQZrl6PZA== dependencies: - "@typescript-eslint/scope-manager" "5.29.0" - "@typescript-eslint/types" "5.29.0" - "@typescript-eslint/typescript-estree" "5.29.0" + "@typescript-eslint/scope-manager" "5.41.0" + "@typescript-eslint/types" "5.41.0" + "@typescript-eslint/typescript-estree" "5.41.0" debug "^4.3.4" -"@typescript-eslint/scope-manager@5.29.0": - version "5.29.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.29.0.tgz#2a6a32e3416cb133e9af8dcf54bf077a916aeed3" - integrity sha512-etbXUT0FygFi2ihcxDZjz21LtC+Eps9V2xVx09zFoN44RRHPrkMflidGMI+2dUs821zR1tDS6Oc9IXxIjOUZwA== +"@typescript-eslint/scope-manager@5.41.0": + version "5.41.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.41.0.tgz#28e3a41d626288d0628be14cf9de8d49fc30fadf" + integrity sha512-xOxPJCnuktUkY2xoEZBKXO5DBCugFzjrVndKdUnyQr3+9aDWZReKq9MhaoVnbL+maVwWJu/N0SEtrtEUNb62QQ== dependencies: - "@typescript-eslint/types" "5.29.0" - "@typescript-eslint/visitor-keys" "5.29.0" + "@typescript-eslint/types" "5.41.0" + "@typescript-eslint/visitor-keys" "5.41.0" -"@typescript-eslint/type-utils@5.29.0": - version "5.29.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.29.0.tgz#241918001d164044020b37d26d5b9f4e37cc3d5d" - integrity sha512-JK6bAaaiJozbox3K220VRfCzLa9n0ib/J+FHIwnaV3Enw/TO267qe0pM1b1QrrEuy6xun374XEAsRlA86JJnyg== +"@typescript-eslint/type-utils@5.41.0": + version "5.41.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.41.0.tgz#2371601171e9f26a4e6da918a7913f7266890cdf" + integrity sha512-L30HNvIG6A1Q0R58e4hu4h+fZqaO909UcnnPbwKiN6Rc3BUEx6ez2wgN7aC0cBfcAjZfwkzE+E2PQQ9nEuoqfA== dependencies: - "@typescript-eslint/utils" "5.29.0" + "@typescript-eslint/typescript-estree" "5.41.0" + "@typescript-eslint/utils" "5.41.0" debug "^4.3.4" tsutils "^3.21.0" -"@typescript-eslint/types@5.29.0": - version "5.29.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.29.0.tgz#7861d3d288c031703b2d97bc113696b4d8c19aab" - integrity sha512-X99VbqvAXOMdVyfFmksMy3u8p8yoRGITgU1joBJPzeYa0rhdf5ok9S56/itRoUSh99fiDoMtarSIJXo7H/SnOg== +"@typescript-eslint/types@5.41.0": + version "5.41.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.41.0.tgz#6800abebc4e6abaf24cdf220fb4ce28f4ab09a85" + integrity sha512-5BejraMXMC+2UjefDvrH0Fo/eLwZRV6859SXRg+FgbhA0R0l6lDqDGAQYhKbXhPN2ofk2kY5sgGyLNL907UXpA== -"@typescript-eslint/typescript-estree@5.29.0": - version "5.29.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.29.0.tgz#e83d19aa7fd2e74616aab2f25dfbe4de4f0b5577" - integrity sha512-mQvSUJ/JjGBdvo+1LwC+GY2XmSYjK1nAaVw2emp/E61wEVYEyibRHCqm1I1vEKbXCpUKuW4G7u9ZCaZhJbLoNQ== +"@typescript-eslint/typescript-estree@5.41.0": + version "5.41.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.41.0.tgz#bf5c6b3138adbdc73ba4871d060ae12c59366c61" + integrity sha512-SlzFYRwFSvswzDSQ/zPkIWcHv8O5y42YUskko9c4ki+fV6HATsTODUPbRbcGDFYP86gaJL5xohUEytvyNNcXWg== dependencies: - "@typescript-eslint/types" "5.29.0" - "@typescript-eslint/visitor-keys" "5.29.0" + "@typescript-eslint/types" "5.41.0" + "@typescript-eslint/visitor-keys" "5.41.0" debug "^4.3.4" globby "^11.1.0" is-glob "^4.0.3" semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/utils@5.29.0": - version "5.29.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.29.0.tgz#775046effd5019667bd086bcf326acbe32cd0082" - integrity sha512-3Eos6uP1nyLOBayc/VUdKZikV90HahXE5Dx9L5YlSd/7ylQPXhLk1BYb29SDgnBnTp+jmSZUU0QxUiyHgW4p7A== +"@typescript-eslint/utils@5.41.0": + version "5.41.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.41.0.tgz#f41ae5883994a249d00b2ce69f4188f3a23fa0f9" + integrity sha512-QlvfwaN9jaMga9EBazQ+5DDx/4sAdqDkcs05AsQHMaopluVCUyu1bTRUVKzXbgjDlrRAQrYVoi/sXJ9fmG+KLQ== dependencies: "@types/json-schema" "^7.0.9" - "@typescript-eslint/scope-manager" "5.29.0" - "@typescript-eslint/types" "5.29.0" - "@typescript-eslint/typescript-estree" "5.29.0" + "@types/semver" "^7.3.12" + "@typescript-eslint/scope-manager" "5.41.0" + "@typescript-eslint/types" "5.41.0" + "@typescript-eslint/typescript-estree" "5.41.0" eslint-scope "^5.1.1" eslint-utils "^3.0.0" + semver "^7.3.7" -"@typescript-eslint/visitor-keys@5.29.0": - version "5.29.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.29.0.tgz#7a4749fa7ef5160c44a451bf060ac1dc6dfb77ee" - integrity sha512-Hpb/mCWsjILvikMQoZIE3voc9wtQcS0A9FUw3h8bhr9UxBdtI/tw1ZDZUOXHXLOVMedKCH5NxyzATwnU78bWCQ== +"@typescript-eslint/visitor-keys@5.41.0": + version "5.41.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.41.0.tgz#d3510712bc07d5540160ed3c0f8f213b73e3bcd9" + integrity sha512-vilqeHj267v8uzzakbm13HkPMl7cbYpKVjgFWZPIOHIJHZtinvypUhJ5xBXfWYg4eFKqztbMMpOgFpT9Gfx4fw== dependencies: - "@typescript-eslint/types" "5.29.0" + "@typescript-eslint/types" "5.41.0" eslint-visitor-keys "^3.3.0" -"@webassemblyjs/ast@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.9.0.tgz#bd850604b4042459a5a41cd7d338cbed695ed964" - integrity sha512-C6wW5L+b7ogSDVqymbkkvuW9kruN//YisMED04xzeBBqjHa2FYnmvOlS6Xj68xWQRgWvI9cIglsjFowH/RJyEA== - dependencies: - "@webassemblyjs/helper-module-context" "1.9.0" - "@webassemblyjs/helper-wasm-bytecode" "1.9.0" - "@webassemblyjs/wast-parser" "1.9.0" - -"@webassemblyjs/floating-point-hex-parser@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.9.0.tgz#3c3d3b271bddfc84deb00f71344438311d52ffb4" - integrity sha512-TG5qcFsS8QB4g4MhrxK5TqfdNe7Ey/7YL/xN+36rRjl/BlGE/NcBvJcqsRgCP6Z92mRE+7N50pRIi8SmKUbcQA== - -"@webassemblyjs/helper-api-error@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.9.0.tgz#203f676e333b96c9da2eeab3ccef33c45928b6a2" - integrity sha512-NcMLjoFMXpsASZFxJ5h2HZRcEhDkvnNFOAKneP5RbKRzaWJN36NC4jqQHKwStIhGXu5mUWlUUk7ygdtrO8lbmw== - -"@webassemblyjs/helper-buffer@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.9.0.tgz#a1442d269c5feb23fcbc9ef759dac3547f29de00" - integrity sha512-qZol43oqhq6yBPx7YM3m9Bv7WMV9Eevj6kMi6InKOuZxhw+q9hOkvq5e/PpKSiLfyetpaBnogSbNCfBwyB00CA== - -"@webassemblyjs/helper-code-frame@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.9.0.tgz#647f8892cd2043a82ac0c8c5e75c36f1d9159f27" - integrity sha512-ERCYdJBkD9Vu4vtjUYe8LZruWuNIToYq/ME22igL+2vj2dQ2OOujIZr3MEFvfEaqKoVqpsFKAGsRdBSBjrIvZA== - dependencies: - "@webassemblyjs/wast-printer" "1.9.0" - -"@webassemblyjs/helper-fsm@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-fsm/-/helper-fsm-1.9.0.tgz#c05256b71244214671f4b08ec108ad63b70eddb8" - integrity sha512-OPRowhGbshCb5PxJ8LocpdX9Kl0uB4XsAjl6jH/dWKlk/mzsANvhwbiULsaiqT5GZGT9qinTICdj6PLuM5gslw== - -"@webassemblyjs/helper-module-context@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-module-context/-/helper-module-context-1.9.0.tgz#25d8884b76839871a08a6c6f806c3979ef712f07" - integrity sha512-MJCW8iGC08tMk2enck1aPW+BE5Cw8/7ph/VGZxwyvGbJwjktKkDK7vy7gAmMDx88D7mhDTCNKAW5tED+gZ0W8g== - dependencies: - "@webassemblyjs/ast" "1.9.0" - -"@webassemblyjs/helper-wasm-bytecode@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.9.0.tgz#4fed8beac9b8c14f8c58b70d124d549dd1fe5790" - integrity sha512-R7FStIzyNcd7xKxCZH5lE0Bqy+hGTwS3LJjuv1ZVxd9O7eHCedSdrId/hMOd20I+v8wDXEn+bjfKDLzTepoaUw== - -"@webassemblyjs/helper-wasm-section@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.9.0.tgz#5a4138d5a6292ba18b04c5ae49717e4167965346" - integrity sha512-XnMB8l3ek4tvrKUUku+IVaXNHz2YsJyOOmz+MMkZvh8h1uSJpSen6vYnw3IoQ7WwEuAhL8Efjms1ZWjqh2agvw== - dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/helper-buffer" "1.9.0" - "@webassemblyjs/helper-wasm-bytecode" "1.9.0" - "@webassemblyjs/wasm-gen" "1.9.0" - -"@webassemblyjs/ieee754@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.9.0.tgz#15c7a0fbaae83fb26143bbacf6d6df1702ad39e4" - integrity sha512-dcX8JuYU/gvymzIHc9DgxTzUUTLexWwt8uCTWP3otys596io0L5aW02Gb1RjYpx2+0Jus1h4ZFqjla7umFniTg== - dependencies: - "@xtuc/ieee754" "^1.2.0" - -"@webassemblyjs/leb128@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.9.0.tgz#f19ca0b76a6dc55623a09cffa769e838fa1e1c95" - integrity sha512-ENVzM5VwV1ojs9jam6vPys97B/S65YQtv/aanqnU7D8aSoHFX8GyhGg0CMfyKNIHBuAVjy3tlzd5QMMINa7wpw== - dependencies: - "@xtuc/long" "4.2.2" - -"@webassemblyjs/utf8@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.9.0.tgz#04d33b636f78e6a6813227e82402f7637b6229ab" - integrity sha512-GZbQlWtopBTP0u7cHrEx+73yZKrQoBMpwkGEIqlacljhXCkVM1kMQge/Mf+csMJAjEdSwhOyLAS0AoR3AG5P8w== - -"@webassemblyjs/wasm-edit@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.9.0.tgz#3fe6d79d3f0f922183aa86002c42dd256cfee9cf" - integrity sha512-FgHzBm80uwz5M8WKnMTn6j/sVbqilPdQXTWraSjBwFXSYGirpkSWE2R9Qvz9tNiTKQvoKILpCuTjBKzOIm0nxw== - dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/helper-buffer" "1.9.0" - "@webassemblyjs/helper-wasm-bytecode" "1.9.0" - "@webassemblyjs/helper-wasm-section" "1.9.0" - "@webassemblyjs/wasm-gen" "1.9.0" - "@webassemblyjs/wasm-opt" "1.9.0" - "@webassemblyjs/wasm-parser" "1.9.0" - "@webassemblyjs/wast-printer" "1.9.0" - -"@webassemblyjs/wasm-gen@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.9.0.tgz#50bc70ec68ded8e2763b01a1418bf43491a7a49c" - integrity sha512-cPE3o44YzOOHvlsb4+E9qSqjc9Qf9Na1OO/BHFy4OI91XDE14MjFN4lTMezzaIWdPqHnsTodGGNP+iRSYfGkjA== - dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/helper-wasm-bytecode" "1.9.0" - "@webassemblyjs/ieee754" "1.9.0" - "@webassemblyjs/leb128" "1.9.0" - "@webassemblyjs/utf8" "1.9.0" - -"@webassemblyjs/wasm-opt@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.9.0.tgz#2211181e5b31326443cc8112eb9f0b9028721a61" - integrity sha512-Qkjgm6Anhm+OMbIL0iokO7meajkzQD71ioelnfPEj6r4eOFuqm4YC3VBPqXjFyyNwowzbMD+hizmprP/Fwkl2A== - dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/helper-buffer" "1.9.0" - "@webassemblyjs/wasm-gen" "1.9.0" - "@webassemblyjs/wasm-parser" "1.9.0" - -"@webassemblyjs/wasm-parser@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.9.0.tgz#9d48e44826df4a6598294aa6c87469d642fff65e" - integrity sha512-9+wkMowR2AmdSWQzsPEjFU7njh8HTO5MqO8vjwEHuM+AMHioNqSBONRdr0NQQ3dVQrzp0s8lTcYqzUdb7YgELA== - dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/helper-api-error" "1.9.0" - "@webassemblyjs/helper-wasm-bytecode" "1.9.0" - "@webassemblyjs/ieee754" "1.9.0" - "@webassemblyjs/leb128" "1.9.0" - "@webassemblyjs/utf8" "1.9.0" - -"@webassemblyjs/wast-parser@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-parser/-/wast-parser-1.9.0.tgz#3031115d79ac5bd261556cecc3fa90a3ef451914" - integrity sha512-qsqSAP3QQ3LyZjNC/0jBJ/ToSxfYJ8kYyuiGvtn/8MK89VrNEfwj7BPQzJVHi0jGTRK2dGdJ5PRqhtjzoww+bw== - dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/floating-point-hex-parser" "1.9.0" - "@webassemblyjs/helper-api-error" "1.9.0" - "@webassemblyjs/helper-code-frame" "1.9.0" - "@webassemblyjs/helper-fsm" "1.9.0" - "@xtuc/long" "4.2.2" - -"@webassemblyjs/wast-printer@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.9.0.tgz#4935d54c85fef637b00ce9f52377451d00d47899" - integrity sha512-2J0nE95rHXHyQ24cWjMKJ1tqB/ds8z/cyeOZxJhcb+rW+SQASVjuznUSmdz5GpVJTzU8JkhYut0D3siFDD6wsA== - dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/wast-parser" "1.9.0" - "@xtuc/long" "4.2.2" - -"@xtuc/ieee754@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" - -"@xtuc/long@4.2.2": - version "4.2.2" - resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" - -"@yarnpkg/lockfile@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31" - -abab@^2.0.5: - version "2.0.6" - resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291" - integrity sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA== - -abbrev@1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" - -accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.7: - version "1.3.7" - resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" +"@vitejs/plugin-react@2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@vitejs/plugin-react/-/plugin-react-2.2.0.tgz#1b9f63b8b6bc3f56258d20cd19b33f5cc761ce6e" + integrity sha512-FFpefhvExd1toVRlokZgxgy2JtnBOdp4ZDsq7ldCWaqGSGn9UhWMAVm/1lxPL14JfNS5yGz+s9yFrQY6shoStA== dependencies: - mime-types "~2.1.24" - negotiator "0.6.2" + "@babel/core" "^7.19.6" + "@babel/plugin-transform-react-jsx" "^7.19.0" + "@babel/plugin-transform-react-jsx-development" "^7.18.6" + "@babel/plugin-transform-react-jsx-self" "^7.18.6" + "@babel/plugin-transform-react-jsx-source" "^7.19.6" + magic-string "^0.26.7" + react-refresh "^0.14.0" acorn-jsx@^5.3.2: version "5.3.2" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== -acorn-node@^1.6.1: +acorn-node@^1.6.1, acorn-node@^1.8.2: version "1.8.2" resolved "https://registry.yarnpkg.com/acorn-node/-/acorn-node-1.8.2.tgz#114c95d64539e53dede23de8b9d96df7c7ae2af8" integrity sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A== @@ -2125,44 +1406,27 @@ acorn-node@^1.6.1: acorn-walk "^7.0.0" xtend "^4.0.2" -acorn-walk@^7.0.0, acorn-walk@^7.1.1: +acorn-walk@^7.0.0: version "7.2.0" resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc" integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA== -acorn@^6.4.1: - version "6.4.1" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.1.tgz#531e58ba3f51b9dacb9a6646ca4debf5b14ca474" - integrity sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA== - -acorn@^7.0.0, acorn@^7.1.1: +acorn@^7.0.0: version "7.3.1" resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.3.1.tgz#85010754db53c3fbaf3b9ea3e083aa5c5d147ffd" integrity sha512-tLc0wSnatxAQHVHUapaHdz72pi9KUyHjq5KyHjGg9Y8Ifdc79pTh2XvI6I1/chZbnM7QtNKzh66ooDogPZSleA== -acorn@^8.5.0, acorn@^8.7.1: +acorn@^8.7.1: version "8.7.1" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.1.tgz#0197122c843d1bf6d0a5e83220a788f278f63c30" integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A== -aggregate-error@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" - integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA== - dependencies: - clean-stack "^2.0.0" - indent-string "^4.0.0" - -ajv-errors@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/ajv-errors/-/ajv-errors-1.0.1.tgz#f35986aceb91afadec4102fbd85014950cefa64d" - -ajv-keywords@^3.1.0, ajv-keywords@^3.4.1, ajv-keywords@^3.5.2: - version "3.5.2" - resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" - integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== +acorn@^8.8.0: + version "8.8.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.1.tgz#0a3f9cbecc4ec3bea6f0a80b66ae8dd2da250b73" + integrity sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA== -ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.2, ajv@^6.12.4, ajv@^6.12.5: +ajv@^6.10.0, ajv@^6.12.4: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== @@ -2172,46 +1436,19 @@ ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.2, ajv@^6.12.4, ajv@^6.12.5: json-schema-traverse "^0.4.1" uri-js "^4.2.2" -ansi-colors@^3.0.0: - version "3.2.3" - resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.3.tgz#57d35b8686e851e2cc04c403f1c00203976a1813" - -ansi-escapes@^4.2.1: - version "4.3.2" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" - integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== - dependencies: - type-fest "^0.21.3" - -ansi-html@0.0.7: - version "0.0.7" - resolved "https://registry.yarnpkg.com/ansi-html/-/ansi-html-0.0.7.tgz#813584021962a9e9e6fd039f940d12f56ca7859e" - -ansi-regex@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" - -ansi-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" - -ansi-regex@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" - ansi-regex@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== -ansi-styles@^3.2.0, ansi-styles@^3.2.1: +ansi-styles@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== dependencies: color-convert "^1.9.0" -ansi-styles@^4.0.0, ansi-styles@^4.1.0: +ansi-styles@^4.1.0: version "4.3.0" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== @@ -2223,14 +1460,7 @@ ansi-styles@^5.0.0: resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== -anymatch@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" - dependencies: - micromatch "^3.1.4" - normalize-path "^2.1.1" - -anymatch@^3.0.3, anymatch@~3.1.2: +anymatch@~3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== @@ -2238,27 +1468,15 @@ anymatch@^3.0.3, anymatch@~3.1.2: normalize-path "^3.0.0" picomatch "^2.0.4" -aproba@^1.0.3, aproba@^1.1.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" - -are-we-there-yet@~1.1.2: - version "1.1.5" - resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21" - dependencies: - delegates "^1.0.0" - readable-stream "^2.0.6" - arg@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/arg/-/arg-5.0.1.tgz#eb0c9a8f77786cad2af8ff2b862899842d7b6adb" integrity sha512-e0hDa9H2Z9AwFkk2qDlwhoMYE4eToKarchkQHovNdLTCYMHZHeRjI71crOh+dio4K6u1IcwubQqo79Ga4CyAQA== -argparse@^1.0.7: - version "1.0.10" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" - dependencies: - sprintf-js "~1.0.2" +arg@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/arg/-/arg-5.0.2.tgz#c81433cc427c92c4dcf4865142dbca6f15acd59c" + integrity sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg== argparse@^2.0.1: version "2.0.1" @@ -2277,26 +1495,6 @@ aria-query@^5.0.0: resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.0.0.tgz#210c21aaf469613ee8c9a62c7f86525e058db52c" integrity sha512-V+SM7AbUwJ+EBnB8+DXs0hPZHO0W6pqBcc0dW90OwtVG02PswOu/teuARoLQjdDOH+t9pJgGnW5/Qmouf3gPJg== -arr-diff@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" - -arr-flatten@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" - -arr-union@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" - -array-flatten@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" - -array-flatten@^2.1.0: - version "2.1.2" - resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-2.1.2.tgz#24ef80a28c1a893617e2149b0c6d0d788293b099" - array-includes@^3.1.2, array-includes@^3.1.5: version "3.1.5" resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.5.tgz#2c320010db8d31031fd2a5f6b3bbd4b1aad31bdb" @@ -2308,25 +1506,11 @@ array-includes@^3.1.2, array-includes@^3.1.5: get-intrinsic "^1.1.1" is-string "^1.0.7" -array-union@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39" - 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" - -array-unique@^0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" - array.prototype.flatmap@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.0.tgz#a7e8ed4225f4788a70cd910abcf0791e76a5534f" @@ -2337,54 +1521,34 @@ array.prototype.flatmap@^1.3.0: es-abstract "^1.19.2" es-shim-unscopables "^1.0.0" -asn1.js@^4.0.0: - version "4.10.1" - resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-4.10.1.tgz#b9c2bf5805f1e64aadeed6df3a2bfafb5a73f5a0" - dependencies: - bn.js "^4.0.0" - inherits "^2.0.1" - minimalistic-assert "^1.0.0" - -assert@^1.1.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/assert/-/assert-1.4.1.tgz#99912d591836b5a6f5b345c0f07eefc08fc65d91" - dependencies: - util "0.10.3" - -assign-symbols@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" - -async-each@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d" - -async-limiter@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8" +asap@~2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" + integrity sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA== -async@^2.6.2: - version "2.6.3" - resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff" - dependencies: - lodash "^4.17.14" +assertion-error@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b" + integrity sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw== asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== -at-least-node@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" - integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== - -atob@^2.1.1, atob@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" - integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== +autoprefixer@10.4.12, autoprefixer@^10.4.11: + version "10.4.12" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.12.tgz#183f30bf0b0722af54ee5ef257f7d4320bb33129" + integrity sha512-WrCGV9/b97Pa+jtwf5UGaRjgQIg7OK3D06GnoYoZNcG1Xb8Gt3EfuKjlhh9i/VtT16g6PYjZ69jdJ2g8FxSC4Q== + dependencies: + browserslist "^4.21.4" + caniuse-lite "^1.0.30001407" + fraction.js "^4.2.0" + normalize-range "^0.1.2" + picocolors "^1.0.0" + postcss-value-parser "^4.2.0" -autoprefixer@^10.2.5, autoprefixer@^10.4.7: +autoprefixer@^10.2.5: version "10.4.7" resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.7.tgz#1db8d195f41a52ca5069b7593be167618edbbedf" integrity sha512-ypHju4Y2Oav95SipEcCcI5J7CGPuvz8oat7sUtYj3ClK44bldfvtvcxK6IEK++7rqB7YchDGzweZIBG+SD0ZAA== @@ -2396,7 +1560,7 @@ autoprefixer@^10.2.5, autoprefixer@^10.4.7: picocolors "^1.0.0" postcss-value-parser "^4.2.0" -axios@^0.27.2: +axios@0.27.2: version "0.27.2" resolved "https://registry.yarnpkg.com/axios/-/axios-0.27.2.tgz#207658cc8621606e586c85db4b41a750e756d972" integrity sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ== @@ -2404,57 +1568,6 @@ axios@^0.27.2: follow-redirects "^1.14.9" form-data "^4.0.0" -babel-jest@^28.1.1: - version "28.1.1" - resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-28.1.1.tgz#2a3a4ae50964695b2d694ccffe4bec537c5a3586" - integrity sha512-MEt0263viUdAkTq5D7upHPNxvt4n9uLUGa6pPz3WviNBMtOmStb1lIXS3QobnoqM+qnH+vr4EKlvhe8QcmxIYw== - dependencies: - "@jest/transform" "^28.1.1" - "@types/babel__core" "^7.1.14" - babel-plugin-istanbul "^6.1.1" - babel-preset-jest "^28.1.1" - chalk "^4.0.0" - graceful-fs "^4.2.9" - slash "^3.0.0" - -babel-loader@^8.2.5: - version "8.2.5" - resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.2.5.tgz#d45f585e654d5a5d90f5350a779d7647c5ed512e" - integrity sha512-OSiFfH89LrEMiWd4pLNqGz4CwJDtbs2ZVc+iGu2HrkRfPxId9F2anQj38IxWpmRfsUY0aBZYi1EFcd3mhtRMLQ== - dependencies: - find-cache-dir "^3.3.1" - loader-utils "^2.0.0" - make-dir "^3.1.0" - schema-utils "^2.6.5" - -babel-plugin-dynamic-import-node@^2.3.3: - version "2.3.3" - resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz#84fda19c976ec5c6defef57f9427b3def66e17a3" - integrity sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ== - dependencies: - object.assign "^4.1.0" - -babel-plugin-istanbul@^6.1.1: - version "6.1.1" - resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73" - integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA== - dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@istanbuljs/load-nyc-config" "^1.0.0" - "@istanbuljs/schema" "^0.1.2" - istanbul-lib-instrument "^5.0.4" - test-exclude "^6.0.0" - -babel-plugin-jest-hoist@^28.1.1: - version "28.1.1" - resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-28.1.1.tgz#5e055cdcc47894f28341f87f5e35aad2df680b11" - integrity sha512-NovGCy5Hn25uMJSAU8FaHqzs13cFoOI4lhIujiepssjCKRsAo3TA734RDWSGxuFTsUJXerYOqQQodlxgmtqbzw== - dependencies: - "@babel/template" "^7.3.3" - "@babel/types" "^7.3.3" - "@types/babel__core" "^7.1.14" - "@types/babel__traverse" "^7.0.6" - babel-plugin-macros@^2.8.0: version "2.8.0" resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz#0f958a7cc6556b1e65344465d99111a1e5e10138" @@ -2464,7 +1577,7 @@ babel-plugin-macros@^2.8.0: cosmiconfig "^6.0.0" resolve "^1.12.0" -"babel-plugin-styled-components@>= 1", babel-plugin-styled-components@^2.0.7: +babel-plugin-styled-components@2.0.7, "babel-plugin-styled-components@>= 1.12.0": version "2.0.7" resolved "https://registry.yarnpkg.com/babel-plugin-styled-components/-/babel-plugin-styled-components-2.0.7.tgz#c81ef34b713f9da2b7d3f5550df0d1e19e798086" integrity sha512-i7YhvPgVqRKfoQ66toiZ06jPNA3p6ierpfUuEWxNF+fV27Uv5gxBkf8KZLHUCc1nFA9j6+80pYoIpqCeyW3/bA== @@ -2479,31 +1592,12 @@ babel-plugin-syntax-jsx@^6.18.0: version "6.18.0" resolved "https://registry.yarnpkg.com/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz#0af32a9a6e13ca7a3fd5069e62d7b0f58d0d8946" -babel-preset-current-node-syntax@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz#b4399239b89b2a011f9ddbe3e4f401fc40cff73b" - integrity sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ== - dependencies: - "@babel/plugin-syntax-async-generators" "^7.8.4" - "@babel/plugin-syntax-bigint" "^7.8.3" - "@babel/plugin-syntax-class-properties" "^7.8.3" - "@babel/plugin-syntax-import-meta" "^7.8.3" - "@babel/plugin-syntax-json-strings" "^7.8.3" - "@babel/plugin-syntax-logical-assignment-operators" "^7.8.3" - "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" - "@babel/plugin-syntax-numeric-separator" "^7.8.3" - "@babel/plugin-syntax-object-rest-spread" "^7.8.3" - "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" - "@babel/plugin-syntax-optional-chaining" "^7.8.3" - "@babel/plugin-syntax-top-level-await" "^7.8.3" - -babel-preset-jest@^28.1.1: - version "28.1.1" - resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-28.1.1.tgz#5b6e5e69f963eb2d70f739c607b8f723c0ee75e4" - integrity sha512-FCq9Oud0ReTeWtcneYf/48981aTfXYuB9gbU4rBNNJVBSQ6ssv7E6v/qvbBxtOWwZFXjLZwpg+W3q7J6vhH25g== - dependencies: - babel-plugin-jest-hoist "^28.1.1" - babel-preset-current-node-syntax "^1.0.0" +babel-plugin-twin@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/babel-plugin-twin/-/babel-plugin-twin-1.0.2.tgz#7ed6622d2a6268fdb76cad414a1f2486df59dc96" + integrity sha512-723f0tizypoy3Fn9ZAuidTCQ+sKh6sn/jX+Cjj3Pk148ew/hUa+fRUyqAPKACIFaVNqRFnZgI1DoNhNMDL+6QA== + dependencies: + "@babel/template" "^7.12.13" babel-runtime@^6.26.0: version "6.26.0" @@ -2518,84 +1612,12 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== -base64-js@^1.0.2: - version "1.3.0" - resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.0.tgz#cab1e6118f051095e58b5281aea8c1cd22bfc0e3" - -base@^0.11.1: - version "0.11.2" - resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" - dependencies: - cache-base "^1.0.1" - class-utils "^0.3.5" - component-emitter "^1.2.1" - define-property "^1.0.0" - isobject "^3.0.1" - mixin-deep "^1.2.0" - pascalcase "^0.1.1" - -batch@0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16" - -bfj@^6.1.1: - version "6.1.2" - resolved "https://registry.yarnpkg.com/bfj/-/bfj-6.1.2.tgz#325c861a822bcb358a41c78a33b8e6e2086dde7f" - integrity sha512-BmBJa4Lip6BPRINSZ0BPEIfB1wUY/9rwbwvIHQA1KjX9om29B6id0wnWXq7m3bn5JrUVjeOTnVuhPT1FiHwPGw== - dependencies: - bluebird "^3.5.5" - check-types "^8.0.3" - hoopy "^0.1.4" - tryer "^1.0.1" - -big.js@^5.2.2: - version "5.2.2" - resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" - -binary-extensions@^1.0.0: - version "1.11.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.11.0.tgz#46aa1751fb6a2f93ee5e689bb1087d4b14c6c205" - binary-extensions@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== -bluebird@^3.5.5: - version "3.5.5" - resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.5.tgz#a8d0afd73251effbbd5fe384a77d73003c17a71f" - -bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0: - version "4.11.8" - resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" - -body-parser@1.19.0: - version "1.19.0" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a" - dependencies: - bytes "3.1.0" - content-type "~1.0.4" - debug "2.6.9" - depd "~1.1.2" - http-errors "1.7.2" - iconv-lite "0.4.24" - on-finished "~2.3.0" - qs "6.7.0" - raw-body "2.4.0" - type-is "~1.6.17" - -bonjour@^3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/bonjour/-/bonjour-3.5.0.tgz#8e890a183d8ee9a2393b3844c691a42bcf7bc9f5" - dependencies: - array-flatten "^2.1.0" - deep-equal "^1.0.1" - dns-equal "^1.0.0" - dns-txt "^2.0.2" - multicast-dns "^6.0.1" - multicast-dns-service-types "^1.1.0" - -boring-avatars@^1.7.0: +boring-avatars@1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/boring-avatars/-/boring-avatars-1.7.0.tgz#70ac7146bbf37d8e69a35544b24f1d75558f868a" integrity sha512-ZNHd8J7C/V0IjQMGQowLJ5rScEFU23WxePigH6rqKcT2Esf0qhYvYxw8s9i3srmlfCnCV00ddBjaoGey1eNOfA== @@ -2608,84 +1630,13 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" -braces@^2.3.1, braces@^2.3.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" - dependencies: - arr-flatten "^1.1.0" - array-unique "^0.3.2" - extend-shallow "^2.0.1" - fill-range "^4.0.0" - isobject "^3.0.1" - repeat-element "^1.1.2" - snapdragon "^0.8.1" - snapdragon-node "^2.0.1" - split-string "^3.0.2" - to-regex "^3.0.1" - braces@^3.0.2, braces@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" dependencies: fill-range "^7.0.1" -brorand@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" - -browserify-aes@^1.0.0, browserify-aes@^1.0.4: - version "1.2.0" - resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48" - dependencies: - buffer-xor "^1.0.3" - cipher-base "^1.0.0" - create-hash "^1.1.0" - evp_bytestokey "^1.0.3" - inherits "^2.0.1" - safe-buffer "^5.0.1" - -browserify-cipher@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/browserify-cipher/-/browserify-cipher-1.0.1.tgz#8d6474c1b870bfdabcd3bcfcc1934a10e94f15f0" - dependencies: - browserify-aes "^1.0.4" - browserify-des "^1.0.0" - evp_bytestokey "^1.0.0" - -browserify-des@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/browserify-des/-/browserify-des-1.0.1.tgz#3343124db6d7ad53e26a8826318712bdc8450f9c" - dependencies: - cipher-base "^1.0.1" - des.js "^1.0.0" - inherits "^2.0.1" - -browserify-rsa@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.0.1.tgz#21e0abfaf6f2029cf2fafb133567a701d4135524" - dependencies: - bn.js "^4.1.0" - randombytes "^2.0.1" - -browserify-sign@^4.0.0: - version "4.0.4" - resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.0.4.tgz#aa4eb68e5d7b658baa6bf6a57e630cbd7a93d298" - dependencies: - bn.js "^4.1.1" - browserify-rsa "^4.0.0" - create-hash "^1.1.0" - create-hmac "^1.1.2" - elliptic "^6.0.0" - inherits "^2.0.1" - parse-asn1 "^5.0.0" - -browserify-zlib@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.2.0.tgz#2869459d9aa3be245fe8fe2ca1f46e2e7f54d73f" - dependencies: - pako "~1.0.5" - -browserslist@^4.20.2, browserslist@^4.20.3, browserslist@^4.8.5: +browserslist@^4.20.3: version "4.21.0" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.0.tgz#7ab19572361a140ecd1e023e2c1ed95edda0cefe" integrity sha512-UQxE0DIhRB5z/zDz9iA03BOfxaN2+GQdBYH/2WrSIWEUrnpzTPJbhqt+umq6r3acaPRTW1FNTkrcp0PXgtFkvA== @@ -2695,110 +1646,25 @@ browserslist@^4.20.2, browserslist@^4.20.3, browserslist@^4.8.5: node-releases "^2.0.5" update-browserslist-db "^1.0.0" -bs-logger@0.x: - version "0.2.6" - resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" - integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== - dependencies: - fast-json-stable-stringify "2.x" - -bser@2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" - integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== +browserslist@^4.21.3, browserslist@^4.21.4: + version "4.21.4" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.4.tgz#e7496bbc67b9e39dd0f98565feccdcb0d4ff6987" + integrity sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw== dependencies: - node-int64 "^0.4.0" + caniuse-lite "^1.0.30001400" + electron-to-chromium "^1.4.251" + node-releases "^2.0.6" + update-browserslist-db "^1.0.9" buffer-from@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.0.tgz#87fcaa3a298358e0ade6e442cfce840740d1ad04" - -buffer-indexof@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/buffer-indexof/-/buffer-indexof-1.1.1.tgz#52fabcc6a606d1a00302802648ef68f639da268c" - -buffer-xor@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" - -buffer@^4.3.0: - version "4.9.1" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.1.tgz#6d1bb601b07a4efced97094132093027c95bc298" - dependencies: - base64-js "^1.0.2" - ieee754 "^1.1.4" - isarray "^1.0.0" - -builtin-status-codes@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" - -bytes@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" + version "1.1.2" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== -bytes@3.1.0, bytes@^3.0.0: +bytes@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" -cacache@^12.0.2: - version "12.0.3" - resolved "https://registry.yarnpkg.com/cacache/-/cacache-12.0.3.tgz#be99abba4e1bf5df461cd5a2c1071fc432573390" - dependencies: - bluebird "^3.5.5" - chownr "^1.1.1" - figgy-pudding "^3.5.1" - glob "^7.1.4" - graceful-fs "^4.1.15" - infer-owner "^1.0.3" - lru-cache "^5.1.1" - mississippi "^3.0.0" - mkdirp "^0.5.1" - move-concurrently "^1.0.1" - promise-inflight "^1.0.1" - rimraf "^2.6.3" - ssri "^6.0.1" - unique-filename "^1.1.1" - y18n "^4.0.0" - -cacache@^15.0.5: - version "15.3.0" - resolved "https://registry.yarnpkg.com/cacache/-/cacache-15.3.0.tgz#dc85380fb2f556fe3dda4c719bfa0ec875a7f1eb" - integrity sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ== - dependencies: - "@npmcli/fs" "^1.0.0" - "@npmcli/move-file" "^1.0.1" - chownr "^2.0.0" - fs-minipass "^2.0.0" - glob "^7.1.4" - infer-owner "^1.0.4" - lru-cache "^6.0.0" - minipass "^3.1.1" - minipass-collect "^1.0.2" - minipass-flush "^1.0.5" - minipass-pipeline "^1.2.2" - mkdirp "^1.0.3" - p-map "^4.0.0" - promise-inflight "^1.0.1" - rimraf "^3.0.2" - ssri "^8.0.1" - tar "^6.0.2" - unique-filename "^1.1.1" - -cache-base@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" - dependencies: - collection-visit "^1.0.0" - component-emitter "^1.2.1" - get-value "^2.0.6" - has-value "^1.0.0" - isobject "^3.0.1" - set-value "^2.0.0" - to-object-path "^0.3.0" - union-value "^1.0.0" - unset-value "^1.0.0" - call-bind@^1.0.0, call-bind@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" @@ -2817,15 +1683,6 @@ camelcase-css@^2.0.1: resolved "https://registry.yarnpkg.com/camelcase-css/-/camelcase-css-2.0.1.tgz#ee978f6947914cc30c6b44741b6ed1df7f043fd5" integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA== -camelcase@^5.0.0, camelcase@^5.3.1: - version "5.3.1" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" - -camelcase@^6.2.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" - integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== - camelize@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/camelize/-/camelize-1.0.0.tgz#164a5483e630fa4321e5af07020e531831b2609b" @@ -2835,7 +1692,30 @@ caniuse-lite@^1.0.30001335, caniuse-lite@^1.0.30001358: resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001359.tgz#a1c1cbe1c2da9e689638813618b4219acbd4925e" integrity sha512-Xln/BAsPzEuiVLgJ2/45IaqD9jShtk3Y33anKb4+yLwQzws3+v6odKfpgES/cDEaZMLzSChpIGdbOYtH9MyuHw== -chalk@^2.0, chalk@^2.0.0, chalk@^2.4.1, chalk@^2.4.2: +caniuse-lite@^1.0.30001400, caniuse-lite@^1.0.30001407: + version "1.0.30001425" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001425.tgz#52917791a453eb3265143d2cd08d80629e82c735" + integrity sha512-/pzFv0OmNG6W0ym80P3NtapU0QEiDS3VuYAZMGoLLqiC7f6FJFe1MjpQDREGApeenD9wloeytmVDj+JLXPC6qw== + +caseless@^0.12.0, caseless@~0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" + integrity sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw== + +chai@^4.3.6: + version "4.3.6" + resolved "https://registry.yarnpkg.com/chai/-/chai-4.3.6.tgz#ffe4ba2d9fa9d6680cc0b370adae709ec9011e9c" + integrity sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q== + dependencies: + assertion-error "^1.1.0" + check-error "^1.0.2" + deep-eql "^3.0.1" + get-func-name "^2.0.0" + loupe "^2.3.1" + pathval "^1.1.1" + type-detect "^4.0.5" + +chalk@^2.0.0: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -2844,14 +1724,6 @@ chalk@^2.0, chalk@^2.0.0, chalk@^2.4.1, chalk@^2.4.2: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" - integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" @@ -2860,40 +1732,17 @@ chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.2: ansi-styles "^4.1.0" supports-color "^7.1.0" -char-regex@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" - integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== - -chart.js@^3.8.0: - version "3.8.0" - resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-3.8.0.tgz#c6c14c457b9dc3ce7f1514a59e9b262afd6f1a94" - integrity sha512-cr8xhrXjLIXVLOBZPkBZVF6NDeiVIrPLHcMhnON7UufudL+CNeRrD+wpYanswlm8NpudMdrt3CHoLMQMxJhHRg== +chart.js@3.9.1: + version "3.9.1" + resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-3.9.1.tgz#3abf2c775169c4c71217a107163ac708515924b8" + integrity sha512-Ro2JbLmvg83gXF5F4sniaQ+lTbSv18E+TIf2cOeiH1Iqd2PGFOtem+DUufMZsCJwFE7ywPOpfXFBwRTGq7dh6w== -check-types@^8.0.3: - version "8.0.3" - resolved "https://registry.yarnpkg.com/check-types/-/check-types-8.0.3.tgz#3356cca19c889544f2d7a95ed49ce508a0ecf552" - integrity sha512-YpeKZngUmG65rLudJ4taU7VLkOCTMhNl/u4ctNC56LQS/zJTyNH0Lrtwm1tfTsbLlwvlfsA2d1c8vCf/Kh2KwQ== - -chokidar@^2.1.8: - version "2.1.8" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917" - dependencies: - anymatch "^2.0.0" - async-each "^1.0.1" - braces "^2.3.2" - glob-parent "^3.1.0" - inherits "^2.0.3" - is-binary-path "^1.0.0" - is-glob "^4.0.0" - normalize-path "^3.0.0" - path-is-absolute "^1.0.0" - readdirp "^2.2.1" - upath "^1.1.1" - optionalDependencies: - fsevents "^1.2.7" +check-error@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82" + integrity sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA== -chokidar@^3.4.0, chokidar@^3.4.2, chokidar@^3.5.2, chokidar@^3.5.3: +chokidar@^3.5.2, chokidar@^3.5.3: version "3.5.3" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== @@ -2908,105 +1757,21 @@ chokidar@^3.4.0, chokidar@^3.4.2, chokidar@^3.5.2, chokidar@^3.5.3: optionalDependencies: fsevents "~2.3.2" -chownr@^1.0.1, chownr@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.1.tgz#54726b8b8fff4df053c42187e801fb4412df1494" - -chownr@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" - integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== - -chrome-trace-event@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz#234090ee97c7d4ad1a2c4beae27505deffc608a4" - dependencies: - tslib "^1.9.0" - -ci-info@^3.2.0: - version "3.3.2" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.3.2.tgz#6d2967ffa407466481c6c90b6e16b3098f080128" - integrity sha512-xmDt/QIAdeZ9+nfdPsaBCpMvHNLFiLdjj59qjqn+6iPe6YmHGQ35sBnQ8uslRBXFmXkiZQOJRjvQeoGppoTjjg== - -cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de" - dependencies: - inherits "^2.0.1" - safe-buffer "^5.0.1" - -cjs-module-lexer@^1.0.0: - version "1.2.2" - resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz#9f84ba3244a512f3a54e5277e8eef4c489864e40" - integrity sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA== - -class-utils@^0.3.5: - version "0.3.6" - resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" - dependencies: - arr-union "^3.1.0" - define-property "^0.2.5" - isobject "^3.0.0" - static-extend "^0.1.1" - -classnames@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.1.tgz#dfcfa3891e306ec1dad105d0e88f4417b8535e8e" - integrity sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA== +classnames@2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.2.tgz#351d813bf0137fcc6a76a16b88208d2560a0d924" + integrity sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw== clean-set@^1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/clean-set/-/clean-set-1.1.2.tgz#76d8bf238c3e27827bfa73073ecdfdc767187070" integrity sha512-cA8uCj0qSoG9e0kevyOWXwPaELRPVg5Pxp6WskLMwerx257Zfnh8Nl0JBH59d7wQzij2CK7qEfJQK3RjuKKIug== -clean-stack@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" - integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== - -cliui@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5" - dependencies: - string-width "^3.1.0" - strip-ansi "^5.2.0" - wrap-ansi "^5.1.0" - -cliui@^7.0.2: - version "7.0.4" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" - integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== - dependencies: - string-width "^4.2.0" - strip-ansi "^6.0.0" - wrap-ansi "^7.0.0" - -co@^4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" - integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== - -code-point-at@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" - -codemirror@^5.57.0: +codemirror@5.57.0: version "5.57.0" resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-5.57.0.tgz#d26365b72f909f5d2dbb6b1209349ca1daeb2d50" integrity sha512-WGc6UL7Hqt+8a6ZAsj/f1ApQl3NPvHY/UQSzG6fB6l4BjExgVdhFaxd7mRTw1UCiYe/6q86zHP+kfvBQcZGvUg== -collect-v8-coverage@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz#cc2c8e94fc18bbdffe64d6534570c8a673b27f59" - integrity sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg== - -collection-visit@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" - dependencies: - map-visit "^1.0.0" - object-visit "^1.0.0" - color-convert@^1.9.0: version "1.9.3" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" @@ -3066,143 +1831,56 @@ color@^4.0.1: color-convert "^2.0.1" color-string "^1.9.0" -combined-stream@^1.0.8: +combined-stream@^1.0.6, combined-stream@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== dependencies: delayed-stream "~1.0.0" -commander@^2.10.0, commander@^2.18.0, commander@^2.20.0: - version "2.20.3" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" - commander@^8.0.0: version "8.3.0" resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66" integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww== -commondir@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" - -component-emitter@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6" - -compressible@~2.0.16: - version "2.0.17" - resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.17.tgz#6e8c108a16ad58384a977f3a482ca20bff2f38c1" - dependencies: - mime-db ">= 1.40.0 < 2" - -compression@^1.7.4: - version "1.7.4" - resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.4.tgz#95523eff170ca57c29a0ca41e6fe131f41e5bb8f" - dependencies: - accepts "~1.3.5" - bytes "3.0.0" - compressible "~2.0.16" - debug "2.6.9" - on-headers "~1.0.2" - safe-buffer "5.1.2" - vary "~1.1.2" - concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== -concat-stream@^1.5.0: +concat-stream@^1.6.0, concat-stream@^1.6.2: version "1.6.2" resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" + integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== dependencies: buffer-from "^1.0.0" inherits "^2.0.3" readable-stream "^2.2.2" typedarray "^0.0.6" -connect-history-api-fallback@^1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz#8b32089359308d111115d81cad3fceab888f97bc" - -console-browserify@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.1.0.tgz#f0241c45730a9fc6323b206dbf38edc741d0bb10" - dependencies: - date-now "^0.1.4" - -console-control-strings@^1.0.0, console-control-strings@~1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" - -constants-browserify@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75" - -content-disposition@0.5.3: - version "0.5.3" - resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd" - dependencies: - safe-buffer "5.1.2" - -content-type@~1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" - -convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0: +convert-source-map@^1.7.0: version "1.8.0" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.8.0.tgz#f3373c32d21b4d780dd8004514684fb791ca4369" integrity sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA== dependencies: safe-buffer "~5.1.1" -cookie-signature@1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" - -cookie@0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" - -copy-concurrently@^1.0.0: - version "1.0.5" - resolved "https://registry.yarnpkg.com/copy-concurrently/-/copy-concurrently-1.0.5.tgz#92297398cae34937fcafd6ec8139c18051f0b5e0" - dependencies: - aproba "^1.1.1" - fs-write-stream-atomic "^1.0.8" - iferr "^0.1.5" - mkdirp "^0.5.1" - rimraf "^2.5.4" - run-queue "^1.0.0" - -copy-descriptor@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" - -copy-to-clipboard@^3.3.1: - version "3.3.1" - resolved "https://registry.yarnpkg.com/copy-to-clipboard/-/copy-to-clipboard-3.3.1.tgz#115aa1a9998ffab6196f93076ad6da3b913662ae" - integrity sha512-i13qo6kIHTTpCm8/Wup+0b1mVWETvu2kIMzKoK8FpkLkFxlt0znUAHcMzox+T8sPlqtZXq3CulEjQHsYiGFJUw== +copy-to-clipboard@3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/copy-to-clipboard/-/copy-to-clipboard-3.3.2.tgz#5b263ec2366224b100181dded7ce0579b340c107" + integrity sha512-Vme1Z6RUDzrb6xAI7EZlVZ5uvOk2F//GaxKUxajDqm9LhOVM1inxNAD2vy+UZDYsd0uyA9s7b3/FVZPSxqrCfg== dependencies: toggle-selection "^1.0.6" -core-js-compat@^3.6.2: - version "3.6.5" - resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.6.5.tgz#2a51d9a4e25dfd6e690251aa81f99e3c05481f1c" - integrity sha512-7ItTKOhOZbznhXAQ2g/slGg1PJV5zDO/WdkTwi7UEOJmkvsE32PWvx6mKtDjiMpjnR2CNf6BAD6sSxIlv7ptng== - dependencies: - browserslist "^4.8.5" - semver "7.0.0" - core-js@^2.4.0: version "2.6.11" resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.11.tgz#38831469f9922bded8ee21c9dc46985e0399308c" integrity sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg== core-util-is@~1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + version "1.0.3" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" + integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== cosmiconfig@^6.0.0: version "6.0.0" @@ -3215,7 +1893,7 @@ cosmiconfig@^6.0.0: path-type "^4.0.0" yaml "^1.7.2" -cosmiconfig@^7.0.0, cosmiconfig@^7.0.1: +cosmiconfig@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.0.1.tgz#714d756522cace867867ccb4474c5d01bbae5d6d" integrity sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ== @@ -3226,38 +1904,15 @@ cosmiconfig@^7.0.0, cosmiconfig@^7.0.1: path-type "^4.0.0" yaml "^1.10.0" -create-ecdh@^4.0.0: - version "4.0.3" - resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.3.tgz#c9111b6f33045c4697f144787f9254cdc77c45ff" - dependencies: - bn.js "^4.1.0" - elliptic "^6.0.0" +crelt@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/crelt/-/crelt-1.0.5.tgz#57c0d52af8c859e354bace1883eb2e1eb182bb94" + integrity sha512-+BO9wPPi+DWTDcNYhr/W90myha8ptzftZT+LwcmUbbok0rcP/fequmFYCw8NMoH7pkAZQzU78b3kYrlua5a9eA== -create-hash@^1.1.0, create-hash@^1.1.2: - version "1.2.0" - resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196" - dependencies: - cipher-base "^1.0.1" - inherits "^2.0.1" - md5.js "^1.3.4" - ripemd160 "^2.0.1" - sha.js "^2.4.0" - -create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4: - version "1.1.7" - resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.7.tgz#69170c78b3ab957147b2b8b04572e47ead2243ff" - dependencies: - cipher-base "^1.0.3" - create-hash "^1.1.0" - inherits "^2.0.1" - ripemd160 "^2.0.0" - safe-buffer "^5.0.1" - sha.js "^2.4.8" - -cross-env@^7.0.2: - version "7.0.2" - resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-7.0.2.tgz#bd5ed31339a93a3418ac4f3ca9ca3403082ae5f9" - integrity sha512-KZP/bMEOJEDCkDQAyRhu3RL2ZO/SUVrxQVI0G3YEQ+OLbRA3c6zgixe8Mq8a/z7+HKlNEjo8oiLUs8iRijY2Rw== +cross-env@7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-7.0.3.tgz#865264b29677dc015ba8418918965dd232fc54cf" + integrity sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw== dependencies: cross-spawn "^7.0.1" @@ -3268,17 +1923,7 @@ cross-fetch@3.1.5: dependencies: node-fetch "2.6.7" -cross-spawn@^6.0.0, cross-spawn@^6.0.5: - version "6.0.5" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" - dependencies: - nice-try "^1.0.4" - path-key "^2.0.1" - semver "^5.5.0" - shebang-command "^1.2.0" - which "^1.2.9" - -cross-spawn@^7.0.1, cross-spawn@^7.0.2, cross-spawn@^7.0.3: +cross-spawn@^7.0.1, cross-spawn@^7.0.2: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== @@ -3287,22 +1932,6 @@ cross-spawn@^7.0.1, cross-spawn@^7.0.2, cross-spawn@^7.0.3: shebang-command "^2.0.0" which "^2.0.1" -crypto-browserify@^3.11.0: - version "3.12.0" - resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" - dependencies: - browserify-cipher "^1.0.0" - browserify-sign "^4.0.0" - create-ecdh "^4.0.0" - create-hash "^1.1.0" - create-hmac "^1.1.0" - diffie-hellman "^5.0.0" - inherits "^2.0.1" - pbkdf2 "^3.0.3" - public-encrypt "^4.0.0" - randombytes "^2.0.0" - randomfill "^1.0.3" - css-blank-pseudo@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/css-blank-pseudo/-/css-blank-pseudo-3.0.3.tgz#36523b01c12a25d812df343a32c322d2a2324561" @@ -3326,22 +1955,6 @@ css-has-pseudo@^3.0.4: dependencies: postcss-selector-parser "^6.0.9" -css-loader@^5.2.7: - version "5.2.7" - resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-5.2.7.tgz#9b9f111edf6fb2be5dc62525644cbc9c232064ae" - integrity sha512-Q7mOvpBNBG7YrVGMxRxcBJZFL75o+cH2abNASdibkj/fffYD8qWbInZrD0S9ccI6vZclF3DsHE7njGlLtaHbhg== - dependencies: - icss-utils "^5.1.0" - loader-utils "^2.0.0" - postcss "^8.2.15" - postcss-modules-extract-imports "^3.0.0" - postcss-modules-local-by-default "^4.0.0" - postcss-modules-scope "^3.0.0" - postcss-modules-values "^4.0.0" - postcss-value-parser "^4.1.0" - schema-utils "^3.0.0" - semver "^7.3.5" - css-prefers-color-scheme@^6.0.3: version "6.0.3" resolved "https://registry.yarnpkg.com/css-prefers-color-scheme/-/css-prefers-color-scheme-6.0.3.tgz#ca8a22e5992c10a5b9d315155e7caee625903349" @@ -3365,62 +1978,29 @@ css.escape@^1.5.1: resolved "https://registry.yarnpkg.com/css.escape/-/css.escape-1.5.1.tgz#42e27d4fa04ae32f931a4b4d4191fa9cddee97cb" integrity sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg== -css@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/css/-/css-3.0.0.tgz#4447a4d58fdd03367c516ca9f64ae365cee4aa5d" - integrity sha512-DG9pFfwOrzc+hawpmqX/dHYHJG+Bsdb0klhyi1sDneOgGOXy9wQIC8hzyVp1e4NRYDBdxcylvywPkkXCHAzTyQ== - dependencies: - inherits "^2.0.4" - source-map "^0.6.1" - source-map-resolve "^0.6.0" - -cssdb@^6.6.3: - version "6.6.3" - resolved "https://registry.yarnpkg.com/cssdb/-/cssdb-6.6.3.tgz#1f331a2fab30c18d9f087301e6122a878bb1e505" - integrity sha512-7GDvDSmE+20+WcSMhP17Q1EVWUrLlbxxpMDqG731n8P99JhnQZHR9YvtjPvEHfjFUjvQJvdpKCjlKOX+xe4UVA== +cssdb@^7.0.1: + version "7.0.2" + resolved "https://registry.yarnpkg.com/cssdb/-/cssdb-7.0.2.tgz#e1cadfe2be318797bd02ca929d2b3c7bac332abc" + integrity sha512-Vm4b6P/PifADu0a76H0DKRNVWq3Rq9xa/Nx6oEMUBJlwTUuZoZ3dkZxo8Gob3UEL53Cq+Ma1GBgISed6XEBs3w== cssesc@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" -csstype@^2.6.7: - version "2.6.7" - resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.7.tgz#20b0024c20b6718f4eda3853a1f5a1cce7f5e4a5" - csstype@^3.0.2: version "3.0.5" resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.5.tgz#7fdec6a28a67ae18647c51668a9ff95bb2fa7bb8" integrity sha512-uVDi8LpBUKQj6sdxNaTetL6FpeCqTjOvAQuQUa/qAqq8oOd4ivkbhgnqayl0dnPal8Tb/yB1tF+gOvCBiicaiQ== -cyclist@~0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-0.2.2.tgz#1b33792e11e914a2fd6d6ed6447464444e5fa640" - -date-fns@^2.28.0: - version "2.28.0" - resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.28.0.tgz#9570d656f5fc13143e50c975a3b6bbeb46cd08b2" - integrity sha512-8d35hViGYx/QH0icHYCeLmsLmMUheMmTyV9Fcm6gvNwdw31yXXH+O85sOBJ+OLnLQMKZowvpKb6FgMIQjcpvQw== - -date-now@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b" - -debounce@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.2.0.tgz#44a540abc0ea9943018dc0eaa95cce87f65cd131" - integrity sha512-mYtLl1xfZLi1m4RtQYlZgJUNQjl4ZxVnHzIR8nLLgi4q1YT8o/WM+MK/f8yfcc9s5Ir5zRaPZyZU6xs1Syoocg== - -debug@2.6.9, debug@^2.1.2, debug@^2.2.0, debug@^2.3.3: - version "2.6.9" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" - dependencies: - ms "2.0.0" +date-fns@2.29.3: + version "2.29.3" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.29.3.tgz#27402d2fc67eb442b511b70bbdf98e6411cd68a8" + integrity sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA== -debug@^3.1.1, debug@^3.2.5: - version "3.2.6" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" - dependencies: - ms "^2.1.1" +debounce@1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.2.1.tgz#38881d8f4166a5c5848020c11827b834bcb3e0a5" + integrity sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug== debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4: version "4.3.4" @@ -3429,53 +2009,27 @@ debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4: dependencies: ms "2.1.2" -decamelize@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" - -decode-uri-component@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" - -dedent@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" - integrity sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA== - -deep-equal@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5" - -deep-extend@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" +deep-eql@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-3.0.1.tgz#dfc9404400ad1c8fe023e7da1df1c147c4b444df" + integrity sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw== + dependencies: + type-detect "^4.0.0" deep-is@^0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= -deepmerge-ts@^4.2.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/deepmerge-ts/-/deepmerge-ts-4.2.1.tgz#104fe27c91abde4597bad1dcfd00a7a8be22d532" - integrity sha512-xzJLiUo4z1dD2nggSfaMvHo5qWLoy/JVa9rKuktC6FrQQEBI8Qnj7KwuCYZhqBoGOOpGqs6+3MR2ZhSMcTr4BA== +deepmerge-ts@4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/deepmerge-ts/-/deepmerge-ts-4.2.2.tgz#582bf34a37592dc8274b137617b539f871aaf11a" + integrity sha512-Ka3Kb21tiWjvQvS9U+1Dx+aqFAHsdTnMdYptLTmC2VAmDFMugWMY1e15aTODstipmCun8iNuqeSfcx6rsUUk0Q== deepmerge@^2.1.1: version "2.2.1" resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-2.2.1.tgz#5d3ff22a01c00f645405a2fbc17d0778a1801170" -deepmerge@^4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" - integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== - -default-gateway@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/default-gateway/-/default-gateway-4.2.0.tgz#167104c7500c2115f6dd69b0a536bb8ed720552b" - dependencies: - execa "^1.0.0" - ip-regex "^2.1.0" - define-properties@^1.1.3, define-properties@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.4.tgz#0b14d7bd7fbeb2f3572c3a7eda80ea5d57fb05b1" @@ -3484,109 +2038,43 @@ define-properties@^1.1.3, define-properties@^1.1.4: has-property-descriptors "^1.0.0" object-keys "^1.1.1" -define-property@^0.2.5: - version "0.2.5" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" - dependencies: - is-descriptor "^0.1.0" - -define-property@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6" - dependencies: - is-descriptor "^1.0.0" - -define-property@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-2.0.2.tgz#d459689e8d654ba77e02a817f8710d702cb16e9d" - dependencies: - is-descriptor "^1.0.2" - isobject "^3.0.1" - defined@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693" -del@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/del/-/del-4.1.1.tgz#9e8f117222ea44a31ff3a156c049b99052a9f0b4" - dependencies: - "@types/glob" "^7.1.1" - globby "^6.1.0" - is-path-cwd "^2.0.0" - is-path-in-cwd "^2.0.0" - p-map "^2.0.0" - pify "^4.0.1" - rimraf "^2.6.3" - delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== -delegates@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" - -depd@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" +dequal@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be" + integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA== -des.js@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.0.tgz#c074d2e2aa6a8a9a07dbd61f9a15c2cd83ec8ecc" +detective@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/detective/-/detective-5.2.0.tgz#feb2a77e85b904ecdea459ad897cc90a99bd2a7b" + integrity sha512-6SsIx+nUUbuK0EthKjv0zrdnajCCXVYGmbYYiYjFVpzcjwEs/JMDZ8tPRG29J/HhN56t3GJp2cGSWDRjjot8Pg== dependencies: - inherits "^2.0.1" - minimalistic-assert "^1.0.0" + acorn-node "^1.6.1" + defined "^1.0.0" + minimist "^1.1.1" -destroy@~1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" - -detect-file@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/detect-file/-/detect-file-1.0.0.tgz#f0d66d03672a825cb1b73bdb3fe62310c8e552b7" - -detect-libc@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" - -detect-newline@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" - integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== - -detect-node@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.4.tgz#014ee8f8f669c5c58023da64b8179c083a28c46c" - -detective@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/detective/-/detective-5.2.0.tgz#feb2a77e85b904ecdea459ad897cc90a99bd2a7b" - integrity sha512-6SsIx+nUUbuK0EthKjv0zrdnajCCXVYGmbYYiYjFVpzcjwEs/JMDZ8tPRG29J/HhN56t3GJp2cGSWDRjjot8Pg== - dependencies: - acorn-node "^1.6.1" - defined "^1.0.0" - minimist "^1.1.1" +detective@^5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/detective/-/detective-5.2.1.tgz#6af01eeda11015acb0e73f933242b70f24f91034" + integrity sha512-v9XE1zRnz1wRtgurGu0Bs8uHKFSTdteYZNbIPFVhUZ39L/S79ppMpdmVOZAnoz1jfEFodc48n6MX483Xo3t1yw== + dependencies: + acorn-node "^1.8.2" + defined "^1.0.0" + minimist "^1.2.6" didyoumean@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/didyoumean/-/didyoumean-1.2.2.tgz#989346ffe9e839b4555ecf5666edea0d3e8ad037" integrity sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw== -diff-sequences@^28.1.1: - version "28.1.1" - resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-28.1.1.tgz#9989dc731266dc2903457a70e996f3a041913ac6" - integrity sha512-FU0iFaH/E23a+a718l8Qa/19bF9p06kgE0KipMOMadwa3SjnaElKzPaUC0vnibs6/B/9ni97s61mcejk8W1fQw== - -diffie-hellman@^5.0.0: - version "5.0.3" - resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" - dependencies: - bn.js "^4.1.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" @@ -3599,23 +2087,6 @@ dlv@^1.1.3: resolved "https://registry.yarnpkg.com/dlv/-/dlv-1.1.3.tgz#5c198a8a11453596e751494d49874bc7732f2e79" integrity sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA== -dns-equal@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d" - -dns-packet@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-1.3.1.tgz#12aa426981075be500b910eedcd0b47dd7deda5a" - dependencies: - ip "^1.1.0" - safe-buffer "^5.0.1" - -dns-txt@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/dns-txt/-/dns-txt-2.0.2.tgz#b91d806f5d27188e4ab3e7d107d881a1cc4642b6" - dependencies: - buffer-indexof "^1.0.0" - doctrine@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" @@ -3629,128 +2100,38 @@ doctrine@^3.0.0: dependencies: esutils "^2.0.2" -dom-accessibility-api@^0.5.6, dom-accessibility-api@^0.5.9: +dom-accessibility-api@^0.5.9: version "0.5.14" resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.14.tgz#56082f71b1dc7aac69d83c4285eef39c15d93f56" integrity sha512-NMt+m9zFMPZe0JcY9gN224Qvk6qLIdqex29clBvc/y75ZBX9YA9wNK3frsYvu2DI1xcCIwxwnX+TlsJ2DSOADg== -dom-helpers@^5.0.1: - version "5.1.3" - resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.1.3.tgz#7233248eb3a2d1f74aafca31e52c5299cc8ce821" - dependencies: - "@babel/runtime" "^7.6.3" - csstype "^2.6.7" - -dom-walk@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.1.tgz#672226dc74c8f799ad35307df936aba11acd6018" - -domain-browser@^1.1.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda" - dset@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/dset/-/dset-2.0.1.tgz#a15fff3d1e4d60ac0c95634625cbd5441a76deb1" integrity sha512-nI29OZMRYq36hOcifB6HTjajNAAiBKSXsyWZrq+VniusseuP2OpNlTiYgsaNRSGvpyq5Wjbc2gQLyBdTyWqhnQ== -duplexer@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1" - integrity sha1-rOb/gIwc5mtX0ev5eXessCM0z8E= - -duplexify@^3.4.2, duplexify@^3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.6.0.tgz#592903f5d80b38d037220541264d69a198fb3410" +easy-peasy@5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/easy-peasy/-/easy-peasy-5.1.0.tgz#5fbd3a2d4803ef982b42083be47badf553227341" + integrity sha512-Gd5jzTRrWrE6r59014GXTzpcq5abJ5AMGxc4fzd9sM8PCy18evzxVF9MYlkkzdTnUI/Rbvf0sQW50ev8DXyNjw== dependencies: - end-of-stream "^1.0.0" - inherits "^2.0.1" - readable-stream "^2.0.0" - stream-shift "^1.0.0" - -easy-peasy@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/easy-peasy/-/easy-peasy-4.0.1.tgz#8b3ab1ebb43509a62dc2c37b4269a9141e33d918" - integrity sha512-aTvB48M2ej6dM/wllUm1F7CTWGnYOYh82SHBkvJtOZhJ/9L8Gmg/nIVqDPwJeojOWZe+gbLtpyi8DhN6fPNBYg== - dependencies: - immer "7.0.9" - is-plain-object "^5.0.0" - memoizerific "^1.11.3" - redux "^4.0.5" - redux-thunk "^2.3.0" - symbol-observable "^2.0.3" - ts-toolbelt "^8.0.7" - use-memo-one "^1.1.1" - -ee-first@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" - -ejs@^2.6.1: - version "2.7.4" - resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.7.4.tgz#48661287573dcc53e366c7a1ae52c3a120eec9ba" - integrity sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA== + "@babel/runtime" "^7.17.2" + fast-deep-equal "^3.1.3" + immer "^9.0.12" + redux "^4.1.2" + redux-thunk "^2.4.1" + ts-toolbelt "^9.6.0" + use-sync-external-store "^1.2.0" electron-to-chromium@^1.4.164: version "1.4.170" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.170.tgz#0415fc489402e09bfbe1f0c99bbf4d73f31d48d4" integrity sha512-rZ8PZLhK4ORPjFqLp9aqC4/S1j4qWFsPPz13xmWdrbBkU/LlxMcok+f+6f8YnQ57MiZwKtOaW15biZZsY5Igvw== -elliptic@^6.0.0: - version "6.4.0" - resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.4.0.tgz#cac9af8762c85836187003c8dfe193e5e2eae5df" - dependencies: - bn.js "^4.4.0" - brorand "^1.0.1" - hash.js "^1.0.0" - hmac-drbg "^1.0.0" - inherits "^2.0.1" - minimalistic-assert "^1.0.0" - minimalistic-crypto-utils "^1.0.0" - -emittery@^0.10.2: - version "0.10.2" - resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.10.2.tgz#902eec8aedb8c41938c46e9385e9db7e03182933" - integrity sha512-aITqOwnLanpHLNXZJENbOgjUBeHocD+xsSJmNrjovKBW5HbSpW3d1pEls7GFQPUWXiwG9+0P4GtHfEqC/4M0Iw== - -emoji-regex@^7.0.1: - version "7.0.3" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" - -emoji-regex@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" - integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== - -emojis-list@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" - integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== - -encodeurl@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" - -end-of-stream@^1.0.0, end-of-stream@^1.1.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43" - dependencies: - once "^1.4.0" - -enhanced-resolve@^4.1.0, enhanced-resolve@^4.1.1: - version "4.2.0" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.2.0.tgz#5d43bda4a0fd447cb0ebbe71bef8deff8805ad0d" - integrity sha512-S7eiFb/erugyd1rLb6mQ3Vuq+EXHv5cpCkNqqIkYkBgN2QdFnyCZzFBleqwGEx4lgNGYij81BWnCrFNK7vxvjQ== - dependencies: - graceful-fs "^4.1.2" - memory-fs "^0.5.0" - tapable "^1.0.0" - -errno@^0.1.3, errno@~0.1.7: - version "0.1.7" - resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.7.tgz#4684d71779ad39af177e3f007996f7c67c852618" - dependencies: - prr "~1.0.1" +electron-to-chromium@^1.4.251: + version "1.4.284" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz#61046d1e4cab3a25238f6bf7413795270f125592" + integrity sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA== error-ex@^1.3.1: version "1.3.2" @@ -3804,31 +2185,150 @@ es-to-primitive@^1.2.1: is-date-object "^1.0.1" is-symbol "^1.0.2" +esbuild-android-64@0.15.12: + version "0.15.12" + resolved "https://registry.yarnpkg.com/esbuild-android-64/-/esbuild-android-64-0.15.12.tgz#5e8151d5f0a748c71a7fbea8cee844ccf008e6fc" + integrity sha512-MJKXwvPY9g0rGps0+U65HlTsM1wUs9lbjt5CU19RESqycGFDRijMDQsh68MtbzkqWSRdEtiKS1mtPzKneaAI0Q== + +esbuild-android-arm64@0.15.12: + version "0.15.12" + resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.15.12.tgz#5ee72a6baa444bc96ffcb472a3ba4aba2cc80666" + integrity sha512-Hc9SEcZbIMhhLcvhr1DH+lrrec9SFTiRzfJ7EGSBZiiw994gfkVV6vG0sLWqQQ6DD7V4+OggB+Hn0IRUdDUqvA== + +esbuild-darwin-64@0.15.12: + version "0.15.12" + resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.15.12.tgz#70047007e093fa1b3ba7ef86f9b3fa63db51fe25" + integrity sha512-qkmqrTVYPFiePt5qFjP8w/S+GIUMbt6k8qmiPraECUWfPptaPJUGkCKrWEfYFRWB7bY23FV95rhvPyh/KARP8Q== + +esbuild-darwin-arm64@0.15.12: + version "0.15.12" + resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.12.tgz#41c951f23d9a70539bcca552bae6e5196696ae04" + integrity sha512-z4zPX02tQ41kcXMyN3c/GfZpIjKoI/BzHrdKUwhC/Ki5BAhWv59A9M8H+iqaRbwpzYrYidTybBwiZAIWCLJAkw== + +esbuild-freebsd-64@0.15.12: + version "0.15.12" + resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.12.tgz#a761b5afd12bbedb7d56c612e9cfa4d2711f33f0" + integrity sha512-XFL7gKMCKXLDiAiBjhLG0XECliXaRLTZh6hsyzqUqPUf/PY4C6EJDTKIeqqPKXaVJ8+fzNek88285krSz1QECw== + +esbuild-freebsd-arm64@0.15.12: + version "0.15.12" + resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.12.tgz#6b0839d4d58deabc6cbd96276eb8cbf94f7f335e" + integrity sha512-jwEIu5UCUk6TjiG1X+KQnCGISI+ILnXzIzt9yDVrhjug2fkYzlLbl0K43q96Q3KB66v6N1UFF0r5Ks4Xo7i72g== + +esbuild-linux-32@0.15.12: + version "0.15.12" + resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.15.12.tgz#bd50bfe22514d434d97d5150977496e2631345b4" + integrity sha512-uSQuSEyF1kVzGzuIr4XM+v7TPKxHjBnLcwv2yPyCz8riV8VUCnO/C4BF3w5dHiVpCd5Z1cebBtZJNlC4anWpwA== + +esbuild-linux-64@0.15.12: + version "0.15.12" + resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.15.12.tgz#074bb2b194bf658245f8490f29c01ffcdfa8c931" + integrity sha512-QcgCKb7zfJxqT9o5z9ZUeGH1k8N6iX1Y7VNsEi5F9+HzN1OIx7ESxtQXDN9jbeUSPiRH1n9cw6gFT3H4qbdvcA== + +esbuild-linux-arm64@0.15.12: + version "0.15.12" + resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.12.tgz#3bf789c4396dc032875a122988efd6f3733f28f5" + integrity sha512-HtNq5xm8fUpZKwWKS2/YGwSfTF+339L4aIA8yphNKYJckd5hVdhfdl6GM2P3HwLSCORS++++7++//ApEwXEuAQ== + +esbuild-linux-arm@0.15.12: + version "0.15.12" + resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.15.12.tgz#b91b5a8d470053f6c2c9c8a5e67ec10a71fe4a67" + integrity sha512-Wf7T0aNylGcLu7hBnzMvsTfEXdEdJY/hY3u36Vla21aY66xR0MS5I1Hw8nVquXjTN0A6fk/vnr32tkC/C2lb0A== + +esbuild-linux-mips64le@0.15.12: + version "0.15.12" + resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.12.tgz#2fb54099ada3c950a7536dfcba46172c61e580e2" + integrity sha512-Qol3+AvivngUZkTVFgLpb0H6DT+N5/zM3V1YgTkryPYFeUvuT5JFNDR3ZiS6LxhyF8EE+fiNtzwlPqMDqVcc6A== + +esbuild-linux-ppc64le@0.15.12: + version "0.15.12" + resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.12.tgz#9e3b8c09825fb27886249dfb3142a750df29a1b7" + integrity sha512-4D8qUCo+CFKaR0cGXtGyVsOI7w7k93Qxb3KFXWr75An0DHamYzq8lt7TNZKoOq/Gh8c40/aKaxvcZnTgQ0TJNg== + +esbuild-linux-riscv64@0.15.12: + version "0.15.12" + resolved "https://registry.yarnpkg.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.12.tgz#923d0f5b6e12ee0d1fe116b08e4ae4478fe40693" + integrity sha512-G9w6NcuuCI6TUUxe6ka0enjZHDnSVK8bO+1qDhMOCtl7Tr78CcZilJj8SGLN00zO5iIlwNRZKHjdMpfFgNn1VA== + +esbuild-linux-s390x@0.15.12: + version "0.15.12" + resolved "https://registry.yarnpkg.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.12.tgz#3b1620220482b96266a0c6d9d471d451a1eab86f" + integrity sha512-Lt6BDnuXbXeqSlVuuUM5z18GkJAZf3ERskGZbAWjrQoi9xbEIsj/hEzVnSAFLtkfLuy2DE4RwTcX02tZFunXww== + +esbuild-netbsd-64@0.15.12: + version "0.15.12" + resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.12.tgz#276730f80da646859b1af5a740e7802d8cd73e42" + integrity sha512-jlUxCiHO1dsqoURZDQts+HK100o0hXfi4t54MNRMCAqKGAV33JCVvMplLAa2FwviSojT/5ZG5HUfG3gstwAG8w== + +esbuild-openbsd-64@0.15.12: + version "0.15.12" + resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.12.tgz#bd0eea1dd2ca0722ed489d88c26714034429f8ae" + integrity sha512-1o1uAfRTMIWNOmpf8v7iudND0L6zRBYSH45sofCZywrcf7NcZA+c7aFsS1YryU+yN7aRppTqdUK1PgbZVaB1Dw== + +esbuild-sunos-64@0.15.12: + version "0.15.12" + resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.15.12.tgz#5e56bf9eef3b2d92360d6d29dcde7722acbecc9e" + integrity sha512-nkl251DpoWoBO9Eq9aFdoIt2yYmp4I3kvQjba3jFKlMXuqQ9A4q+JaqdkCouG3DHgAGnzshzaGu6xofGcXyPXg== + +esbuild-windows-32@0.15.12: + version "0.15.12" + resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.15.12.tgz#a4f1a301c1a2fa7701fcd4b91ef9d2620cf293d0" + integrity sha512-WlGeBZHgPC00O08luIp5B2SP4cNCp/PcS+3Pcg31kdcJPopHxLkdCXtadLU9J82LCfw4TVls21A6lilQ9mzHrw== + +esbuild-windows-64@0.15.12: + version "0.15.12" + resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.15.12.tgz#bc2b467541744d653be4fe64eaa9b0dbbf8e07f6" + integrity sha512-VActO3WnWZSN//xjSfbiGOSyC+wkZtI8I4KlgrTo5oHJM6z3MZZBCuFaZHd8hzf/W9KPhF0lY8OqlmWC9HO5AA== + +esbuild-windows-arm64@0.15.12: + version "0.15.12" + resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.12.tgz#9a7266404334a86be800957eaee9aef94c3df328" + integrity sha512-Of3MIacva1OK/m4zCNIvBfz8VVROBmQT+gRX6pFTLPngFYcj6TFH/12VveAqq1k9VB2l28EoVMNMUCcmsfwyuA== + +esbuild@^0.15.9: + version "0.15.12" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.15.12.tgz#6c8e22d6d3b7430d165c33848298d3fc9a1f251c" + integrity sha512-PcT+/wyDqJQsRVhaE9uX/Oq4XLrFh0ce/bs2TJh4CSaw9xuvI+xFrH2nAYOADbhQjUgAhNWC5LKoUsakm4dxng== + optionalDependencies: + "@esbuild/android-arm" "0.15.12" + "@esbuild/linux-loong64" "0.15.12" + esbuild-android-64 "0.15.12" + esbuild-android-arm64 "0.15.12" + esbuild-darwin-64 "0.15.12" + esbuild-darwin-arm64 "0.15.12" + esbuild-freebsd-64 "0.15.12" + esbuild-freebsd-arm64 "0.15.12" + esbuild-linux-32 "0.15.12" + esbuild-linux-64 "0.15.12" + esbuild-linux-arm "0.15.12" + esbuild-linux-arm64 "0.15.12" + esbuild-linux-mips64le "0.15.12" + esbuild-linux-ppc64le "0.15.12" + esbuild-linux-riscv64 "0.15.12" + esbuild-linux-s390x "0.15.12" + esbuild-netbsd-64 "0.15.12" + esbuild-openbsd-64 "0.15.12" + esbuild-sunos-64 "0.15.12" + esbuild-windows-32 "0.15.12" + esbuild-windows-64 "0.15.12" + esbuild-windows-arm64 "0.15.12" + escalade@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== -escape-html@~1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" - escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== -escape-string-regexp@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" - integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== - escape-string-regexp@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== -eslint-config-prettier@^8.5.0: +eslint-config-prettier@8.5.0: version "8.5.0" resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz#5a81680ec934beca02c7b1a61cf8ca34b66feab1" integrity sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q== @@ -3841,16 +2341,7 @@ eslint-plugin-es@^3.0.0: eslint-utils "^2.0.0" regexpp "^3.0.0" -eslint-plugin-jest-dom@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/eslint-plugin-jest-dom/-/eslint-plugin-jest-dom-4.0.2.tgz#9d3e2f51055f74c74e745d89c4b1a9781e0ec7a9" - integrity sha512-Jo51Atwyo2TdcUncjmU+UQeSTKh3sc2LF/M5i/R3nTU0Djw9V65KGJisdm/RtuKhy2KH/r7eQ1n6kwYFPNdHlA== - dependencies: - "@babel/runtime" "^7.16.3" - "@testing-library/dom" "^8.11.1" - requireindex "^1.2.0" - -eslint-plugin-node@^11.1.0: +eslint-plugin-node@11.1.0: version "11.1.0" resolved "https://registry.yarnpkg.com/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz#c95544416ee4ada26740a30474eefc5402dc671d" integrity sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g== @@ -3862,22 +2353,22 @@ eslint-plugin-node@^11.1.0: resolve "^1.10.1" semver "^6.1.0" -eslint-plugin-prettier@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-4.0.0.tgz#8b99d1e4b8b24a762472b4567992023619cb98e0" - integrity sha512-98MqmCJ7vJodoQK359bqQWaxOE0CS8paAz/GgjaZLyex4TTk3g9HugoO89EqWCrFiOqn9EVvcoo7gZzONCWVwQ== +eslint-plugin-prettier@4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz#651cbb88b1dab98bfd42f017a12fa6b2d993f94b" + integrity sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ== dependencies: prettier-linter-helpers "^1.0.0" -eslint-plugin-react-hooks@^4.6.0: +eslint-plugin-react-hooks@4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz#4c3e697ad95b77e93f8646aaa1630c1ba607edd3" integrity sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g== -eslint-plugin-react@^7.30.1: - version "7.30.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.30.1.tgz#2be4ab23ce09b5949c6631413ba64b2810fd3e22" - integrity sha512-NbEvI9jtqO46yJA3wcRF9Mo0lF9T/jhdHqhCHXiXtD+Zcb98812wvokjWpU7Q4QH5edo6dmqrukxVvWWXHlsUg== +eslint-plugin-react@7.31.10: + version "7.31.10" + resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.31.10.tgz#6782c2c7fe91c09e715d536067644bbb9491419a" + integrity sha512-e4N/nc6AAlg4UKW/mXeYWd3R++qUano5/o+t+wnWxIf+bLsOaH3a4q74kX3nDjYym3VBN4HyO9nEn1GcAqgQOA== dependencies: array-includes "^3.1.5" array.prototype.flatmap "^1.3.0" @@ -3894,13 +2385,6 @@ eslint-plugin-react@^7.30.1: semver "^6.3.0" string.prototype.matchall "^4.0.7" -eslint-scope@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848" - dependencies: - esrecurse "^4.1.0" - estraverse "^4.1.1" - eslint-scope@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" @@ -3945,7 +2429,7 @@ eslint-visitor-keys@^3.3.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826" integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== -eslint@^8.18.0: +eslint@8.18.0: version "8.18.0" resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.18.0.tgz#78d565d16c993d0b73968c523c0446b13da784fd" integrity sha512-As1EfFMVk7Xc6/CvhssHUjsAQSkpfXvUGMFC3ce8JDe6WvqCgRrLOBQbVpsBFr1X1V+RACOadnzVvcUS5ni2bA== @@ -3995,10 +2479,6 @@ espree@^9.3.2: acorn-jsx "^5.3.2" eslint-visitor-keys "^3.3.0" -esprima@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.0.tgz#4499eddcd1110e0b218bacf2fa7f7f59f55ca804" - esquery@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5" @@ -4006,7 +2486,7 @@ esquery@^1.4.0: dependencies: estraverse "^5.1.0" -esrecurse@^4.1.0, esrecurse@^4.3.0: +esrecurse@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== @@ -4026,156 +2506,10 @@ esutils@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" -etag@~1.8.1: - version "1.8.1" - resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" - -eventemitter3@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.0.tgz#d65176163887ee59f386d64c82610b696a4a74eb" - -events@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/events/-/events-3.0.0.tgz#9a0a0dfaf62893d92b875b8f2698ca4114973e88" - -eventsource@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-1.0.7.tgz#8fbc72c93fcd34088090bc0a4e64f4b5cee6d8d0" - dependencies: - original "^1.0.0" - -evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02" - dependencies: - md5.js "^1.3.4" - safe-buffer "^5.1.1" - -execa@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8" - dependencies: - cross-spawn "^6.0.0" - get-stream "^4.0.0" - is-stream "^1.1.0" - npm-run-path "^2.0.0" - p-finally "^1.0.0" - signal-exit "^3.0.0" - strip-eof "^1.0.0" - -execa@^5.0.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" - integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== - dependencies: - cross-spawn "^7.0.3" - get-stream "^6.0.0" - human-signals "^2.1.0" - is-stream "^2.0.0" - merge-stream "^2.0.0" - npm-run-path "^4.0.1" - onetime "^5.1.2" - signal-exit "^3.0.3" - strip-final-newline "^2.0.0" - -exit@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" - integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== - -expand-brackets@^2.1.4: - version "2.1.4" - resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" - dependencies: - debug "^2.3.3" - define-property "^0.2.5" - extend-shallow "^2.0.1" - posix-character-classes "^0.1.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - -expand-tilde@^2.0.0, expand-tilde@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-2.0.2.tgz#97e801aa052df02454de46b02bf621642cdc8502" - dependencies: - homedir-polyfill "^1.0.1" - -expect@^28.1.1: - version "28.1.1" - resolved "https://registry.yarnpkg.com/expect/-/expect-28.1.1.tgz#ca6fff65f6517cf7220c2e805a49c19aea30b420" - integrity sha512-/AANEwGL0tWBwzLNOvO0yUdy2D52jVdNXppOqswC49sxMN2cPWsGCQdzuIf9tj6hHoBQzNvx75JUYuQAckPo3w== - dependencies: - "@jest/expect-utils" "^28.1.1" - jest-get-type "^28.0.2" - jest-matcher-utils "^28.1.1" - jest-message-util "^28.1.1" - jest-util "^28.1.1" - -express@^4.16.3, express@^4.17.1: - version "4.17.1" - resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134" - dependencies: - accepts "~1.3.7" - array-flatten "1.1.1" - body-parser "1.19.0" - content-disposition "0.5.3" - content-type "~1.0.4" - cookie "0.4.0" - cookie-signature "1.0.6" - debug "2.6.9" - depd "~1.1.2" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - finalhandler "~1.1.2" - fresh "0.5.2" - merge-descriptors "1.0.1" - methods "~1.1.2" - on-finished "~2.3.0" - parseurl "~1.3.3" - path-to-regexp "0.1.7" - proxy-addr "~2.0.5" - qs "6.7.0" - range-parser "~1.2.1" - safe-buffer "5.1.2" - send "0.17.1" - serve-static "1.14.1" - setprototypeof "1.1.1" - statuses "~1.5.0" - type-is "~1.6.18" - utils-merge "1.0.1" - vary "~1.1.2" - -extend-shallow@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" - dependencies: - is-extendable "^0.1.0" - -extend-shallow@^3.0.0, extend-shallow@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8" - dependencies: - assign-symbols "^1.0.0" - is-extendable "^1.0.1" - -extglob@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" - dependencies: - array-unique "^0.3.2" - define-property "^1.0.0" - expand-brackets "^2.1.4" - extend-shallow "^2.0.1" - fragment-cache "^0.2.1" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - -fast-deep-equal@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49" +events@3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" + integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" @@ -4187,7 +2521,18 @@ fast-diff@^1.1.2: resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03" integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w== -fast-glob@^3.2.11, fast-glob@^3.2.7, fast-glob@^3.2.9: +fast-glob@^3.2.12: + version "3.2.12" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.12.tgz#7f39ec99c2e6ab030337142da9e0c18f37afae80" + integrity sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + +fast-glob@^3.2.7, fast-glob@^3.2.9: version "3.2.11" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9" integrity sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew== @@ -4198,7 +2543,7 @@ fast-glob@^3.2.11, fast-glob@^3.2.7, fast-glob@^3.2.9: merge2 "^1.3.0" micromatch "^4.0.4" -fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0: +fast-json-stable-stringify@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== @@ -4214,29 +2559,6 @@ fastq@^1.6.0: 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" - dependencies: - websocket-driver ">=0.5.1" - -faye-websocket@~0.11.1: - version "0.11.1" - resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.1.tgz#f0efe18c4f56e4f40afc7e06c719fd5ee6188f38" - dependencies: - websocket-driver ">=0.5.1" - -fb-watchman@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.1.tgz#fc84fb39d2709cf3ff6d743706157bb5708a8a85" - integrity sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg== - dependencies: - bser "2.1.1" - -figgy-pudding@^3.5.1: - version "3.5.1" - resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.1.tgz#862470112901c727a0e495a80744bd5baa1d6790" - file-entry-cache@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" @@ -4244,28 +2566,6 @@ file-entry-cache@^6.0.1: dependencies: flat-cache "^3.0.4" -file-loader@~6.2.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-6.2.0.tgz#baef7cf8e1840df325e4390b4484879480eebe4d" - integrity sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw== - dependencies: - loader-utils "^2.0.0" - schema-utils "^3.0.0" - -filesize@^3.6.1: - version "3.6.1" - resolved "https://registry.yarnpkg.com/filesize/-/filesize-3.6.1.tgz#090bb3ee01b6f801a8a8be99d31710b3422bb317" - integrity sha512-7KjR1vv6qnicaPMi1iiTcI85CyYwRO/PSFCu6SvqL8jN2Wjt/NIYQTFtFs7fSDCYOstUkEWIQGFUg5YZQfjlcg== - -fill-range@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" - dependencies: - extend-shallow "^2.0.1" - is-number "^3.0.0" - repeat-string "^1.6.1" - to-regex-range "^2.1.0" - fill-range@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" @@ -4273,59 +2573,6 @@ fill-range@^7.0.1: dependencies: to-regex-range "^5.0.1" -finalhandler@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" - dependencies: - debug "2.6.9" - encodeurl "~1.0.2" - escape-html "~1.0.3" - on-finished "~2.3.0" - parseurl "~1.3.3" - statuses "~1.5.0" - unpipe "~1.0.0" - -find-cache-dir@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz#8d0f94cd13fe43c6c7c261a0d86115ca918c05f7" - dependencies: - commondir "^1.0.1" - make-dir "^2.0.0" - pkg-dir "^3.0.0" - -find-cache-dir@^3.3.1: - version "3.3.1" - resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.3.1.tgz#89b33fad4a4670daa94f855f7fbe31d6d84fe880" - integrity sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ== - dependencies: - commondir "^1.0.1" - make-dir "^3.0.2" - pkg-dir "^4.1.0" - -find-up@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" - dependencies: - locate-path "^3.0.0" - -find-up@^4.0.0, find-up@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" - integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== - dependencies: - locate-path "^5.0.0" - path-exists "^4.0.0" - -findup-sync@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-3.0.0.tgz#17b108f9ee512dfb7a5c7f3c8b27ea9e1a9c08d1" - integrity sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg== - dependencies: - detect-file "^1.0.0" - is-glob "^4.0.0" - micromatch "^3.0.4" - resolve-dir "^1.0.1" - flat-cache@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" @@ -4339,45 +2586,19 @@ flatted@^3.1.0: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.1.1.tgz#c4b489e80096d9df1dfc97c79871aea7c617c469" integrity sha512-zAoAQiudy+r5SvnSw3KJy5os/oRJYHzrzja/tBDqrZtNhUw8bt6y8OBzMWcjWr+8liV8Eb6yOhw8WZ7VFZ5ZzA== -flush-write-stream@^1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.0.3.tgz#c5d586ef38af6097650b49bc41b55fabb19f35bd" - dependencies: - inherits "^2.0.1" - readable-stream "^2.0.4" - -fn-name@~3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/fn-name/-/fn-name-3.0.0.tgz#0596707f635929634d791f452309ab41558e3c5c" - integrity sha512-eNMNr5exLoavuAMhIUVsOKF79SWd/zG104ef6sxBTSw+cZc6BXdQXDvYcGvp0VbxVVSp1XDUNoz7mg1xMtSznA== - -follow-redirects@^1.0.0, follow-redirects@^1.14.9: +follow-redirects@^1.14.9: version "1.15.1" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.1.tgz#0ca6a452306c9b276e4d3127483e29575e207ad5" integrity sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA== -for-in@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" - -fork-ts-checker-webpack-plugin@^6.2.10: - version "6.5.2" - resolved "https://registry.yarnpkg.com/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-6.5.2.tgz#4f67183f2f9eb8ba7df7177ce3cf3e75cdafb340" - integrity sha512-m5cUmF30xkZ7h4tWUgTAcEaKmUW7tfyUyTqNNOz7OxWJ0v1VWKTcOvH8FWHUwSjlW/356Ijc9vi3XfcPstpQKA== +form-data@^2.2.0: + version "2.5.1" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.1.tgz#f2cbec57b5e59e23716e128fe44d4e5dd23895f4" + integrity sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA== dependencies: - "@babel/code-frame" "^7.8.3" - "@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 "2.7.0" - semver "^7.3.2" - tapable "^1.0.0" + asynckit "^0.4.0" + combined-stream "^1.0.6" + mime-types "^2.1.12" form-data@^4.0.0: version "4.0.0" @@ -4388,64 +2609,44 @@ form-data@^4.0.0: combined-stream "^1.0.8" mime-types "^2.1.12" -formik@^2.2.6: - version "2.2.6" - resolved "https://registry.yarnpkg.com/formik/-/formik-2.2.6.tgz#378a4bafe4b95caf6acf6db01f81f3fe5147559d" - integrity sha512-Kxk2zQRafy56zhLmrzcbryUpMBvT0tal5IvcifK5+4YNGelKsnrODFJ0sZQRMQboblWNym4lAW3bt+tf2vApSA== +formik@2.2.9: + version "2.2.9" + resolved "https://registry.yarnpkg.com/formik/-/formik-2.2.9.tgz#8594ba9c5e2e5cf1f42c5704128e119fc46232d0" + integrity sha512-LQLcISMmf1r5at4/gyJigGn0gOwFbeEAlji+N9InZF6LIMXnFNkO42sCI8Jt84YZggpD4cPWObAZaxpEFtSzNA== dependencies: deepmerge "^2.1.1" hoist-non-react-statics "^3.3.0" - lodash "^4.17.14" - lodash-es "^4.17.14" + lodash "^4.17.21" + lodash-es "^4.17.21" react-fast-compare "^2.0.1" tiny-warning "^1.0.2" tslib "^1.10.0" -forwarded@~0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" - fraction.js@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.2.0.tgz#448e5109a313a3527f5a3ab2119ec4cf0e0e2950" integrity sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA== -fragment-cache@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" - dependencies: - map-cache "^0.2.2" - -framer-motion@^6.3.10: - version "6.3.10" - resolved "https://registry.yarnpkg.com/framer-motion/-/framer-motion-6.3.10.tgz#e71f8c4ee09612de8328725c4fb1a1c204ae451f" - integrity sha512-modFplFb1Fznsm0MrmRAJUC32UDA5jbGU9rDvkGzhAHksru2tnoKbU/Pa3orzdsJI0CJviG4NGBrmwGveU98Cg== +framer-motion@7.6.2: + version "7.6.2" + resolved "https://registry.yarnpkg.com/framer-motion/-/framer-motion-7.6.2.tgz#7fb93ebfeda27c8c2cff1895ca7a417229e81bf7" + integrity sha512-YRr+KaC+1MlLx7iArVyjZRpc0QXI7H0XIOJrdol+dF1+WLQJwS2sP04KGq808BG+byD36UAmAt4YqObE5YFLtw== dependencies: - framesync "6.0.1" + "@motionone/dom" "10.13.1" + framesync "6.1.2" hey-listen "^1.0.8" - popmotion "11.0.3" - style-value-types "5.0.0" - tslib "^2.1.0" + popmotion "11.0.5" + style-value-types "5.1.2" + tslib "2.4.0" optionalDependencies: "@emotion/is-prop-valid" "^0.8.2" -framesync@6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/framesync/-/framesync-6.0.1.tgz#5e32fc01f1c42b39c654c35b16440e07a25d6f20" - integrity sha512-fUY88kXvGiIItgNC7wcTOl0SNRCVXMKSWW2Yzfmn7EKNc+MpCzcz9DhdHcdjbrtN3c6R4H5dTY2jiCpPdysEjA== - dependencies: - tslib "^2.1.0" - -fresh@0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" - -from2@^2.1.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/from2/-/from2-2.3.0.tgz#8bfb5502bde4a4d36cfdeea007fcca21d7e382af" +framesync@6.1.2: + version "6.1.2" + resolved "https://registry.yarnpkg.com/framesync/-/framesync-6.1.2.tgz#755eff2fb5b8f3b4d2b266dd18121b300aefea27" + integrity sha512-jBTqhX6KaQVDyus8muwZbBeGGP0XgujBRbQ7gM7BRdS3CadCZIHiawyzYLnafYcvZIh5j8WE7cxZKFn7dXhu9g== dependencies: - inherits "^2.0.1" - readable-stream "^2.0.0" + tslib "2.4.0" fs-extra@^10.0.0: version "10.1.0" @@ -4456,56 +2657,12 @@ fs-extra@^10.0.0: jsonfile "^6.0.1" universalify "^2.0.0" -fs-extra@^9.0.0: - version "9.1.0" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d" - integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ== - dependencies: - at-least-node "^1.0.0" - graceful-fs "^4.2.0" - jsonfile "^6.0.1" - universalify "^2.0.0" - -fs-minipass@^1.2.5: - version "1.2.5" - resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.5.tgz#06c277218454ec288df77ada54a03b8702aacb9d" - dependencies: - minipass "^2.2.1" - -fs-minipass@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" - integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== - dependencies: - minipass "^3.0.0" - -fs-monkey@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/fs-monkey/-/fs-monkey-1.0.3.tgz#ae3ac92d53bb328efe0e9a1d9541f6ad8d48e2d3" - integrity sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q== - -fs-write-stream-atomic@^1.0.8: - version "1.0.10" - resolved "https://registry.yarnpkg.com/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz#b47df53493ef911df75731e70a9ded0189db40c9" - dependencies: - graceful-fs "^4.1.2" - iferr "^0.1.5" - imurmurhash "^0.1.4" - readable-stream "1 || 2" - fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== -fsevents@^1.2.7: - version "1.2.7" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.7.tgz#4851b664a3783e52003b3c66eb0eee1074933aa4" - dependencies: - nan "^2.9.2" - node-pre-gyp "^0.10.0" - -fsevents@^2.3.2, fsevents@~2.3.2: +fsevents@~2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== @@ -4533,27 +2690,15 @@ functions-have-names@^1.2.2: resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== -gauge@~2.7.3: - version "2.7.4" - resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" - dependencies: - aproba "^1.0.3" - console-control-strings "^1.0.0" - has-unicode "^2.0.0" - object-assign "^4.1.0" - signal-exit "^3.0.0" - string-width "^1.0.1" - strip-ansi "^3.0.1" - wide-align "^1.1.0" - gensync@^1.0.0-beta.2: version "1.0.0-beta.2" resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== -get-caller-file@^2.0.1, get-caller-file@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" +get-func-name@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41" + integrity sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig== get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1: version "1.1.1" @@ -4564,21 +2709,10 @@ get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1: has "^1.0.3" has-symbols "^1.0.1" -get-package-type@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" - integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== - -get-stream@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" - dependencies: - pump "^3.0.0" - -get-stream@^6.0.0: - version "6.0.1" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" - integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== +get-port@^3.1.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/get-port/-/get-port-3.2.0.tgz#dd7ce7de187c06c8bf353796ac71e099f0980ebc" + integrity sha512-x5UJKlgeUiNT8nyo/AcnwLnZuZNcSjSw0kogRB+Whd1fjjFq4B1hySFxSFWWSn4mIBzg3sRNUDFYc4g5gjPoLg== get-symbol-description@^1.0.0: version "1.0.0" @@ -4588,17 +2722,6 @@ get-symbol-description@^1.0.0: call-bind "^1.0.2" get-intrinsic "^1.1.1" -get-value@^2.0.3, get-value@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" - -glob-parent@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae" - dependencies: - is-glob "^3.1.0" - path-dirname "^1.0.0" - glob-parent@^5.1.2, glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" @@ -4613,7 +2736,7 @@ glob-parent@^6.0.1, glob-parent@^6.0.2: dependencies: is-glob "^4.0.3" -glob@^7.0.3, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@^7.1.7: +glob@^7.1.3, glob@^7.1.7: version "7.2.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== @@ -4625,46 +2748,6 @@ glob@^7.0.3, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@^7.1.7: 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" - dependencies: - global-prefix "^1.0.1" - is-windows "^1.0.1" - resolve-dir "^1.0.0" - -global-modules@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-2.0.0.tgz#997605ad2345f27f51539bea26574421215c7780" - integrity sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A== - dependencies: - global-prefix "^3.0.0" - -global-prefix@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-1.0.2.tgz#dbf743c6c14992593c655568cb66ed32c0122ebe" - dependencies: - expand-tilde "^2.0.2" - homedir-polyfill "^1.0.1" - ini "^1.3.4" - is-windows "^1.0.1" - which "^1.2.14" - -global-prefix@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-3.0.0.tgz#fc85f73064df69f50421f47f883fe5b913ba9b97" - dependencies: - ini "^1.3.5" - kind-of "^6.0.2" - which "^1.3.1" - -global@^4.3.0: - version "4.3.2" - resolved "https://registry.yarnpkg.com/global/-/global-4.3.2.tgz#e76989268a6c74c38908b1305b10fc0e394e9d0f" - dependencies: - min-document "^2.19.0" - process "~0.5.1" - globals@^11.1.0: version "11.12.0" resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" @@ -4688,41 +2771,23 @@ globby@^11.1.0: merge2 "^1.4.1" slash "^3.0.0" -globby@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/globby/-/globby-6.1.0.tgz#f5a6d70e8395e21c858fb0489d64df02424d506c" - dependencies: - array-union "^1.0.1" - glob "^7.0.3" - object-assign "^4.0.1" - pify "^2.0.0" - pinkie-promise "^2.0.0" - -graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.9: +graceful-fs@^4.1.6, graceful-fs@^4.2.0: version "4.2.10" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== -gud@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/gud/-/gud-1.0.0.tgz#a489581b17e6a70beca9abe3ae57de7a499852c0" - -gzip-size@^5.0.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-5.1.1.tgz#cb9bee692f87c0612b232840a873904e4c135274" - integrity sha512-FNHi6mmoHvs1mxZAds4PpdCS6QG8B4C1krxJsMutgxl5t3+GlRTzzI3NEkifXx2pVsOvJdOGSmIgDhQ55FwdPA== +happy-dom@7.6.6: + version "7.6.6" + resolved "https://registry.yarnpkg.com/happy-dom/-/happy-dom-7.6.6.tgz#f94ce99c5a32d1ea8578b75e8f584e8fcd162513" + integrity sha512-28NxRiHXjzhr+BGciLNUoQW4OaBnQPRT/LPYLufh0Fj3Iwh1j9qJaozjBm/Uqdj5Ps4cukevQ7ERieA6Ddwf1g== dependencies: - duplexer "^0.1.1" - pify "^4.0.1" - -handle-thing@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.0.tgz#0e039695ff50c93fc288557d696f3c1dc6776754" - -harmony-reflect@^1.4.6: - version "1.6.2" - resolved "https://registry.yarnpkg.com/harmony-reflect/-/harmony-reflect-1.6.2.tgz#31ecbd32e648a34d030d86adb67d4d47547fe710" - integrity sha512-HIp/n38R9kQjDEziXyDTuW3vvoxxyxjxFzXLrBr18uB47GnSt+G9D29fqrpM5ZkspMcPICud3XsBJQ4Y2URg8g== + css.escape "^1.5.1" + he "^1.2.0" + node-fetch "^2.x.x" + sync-request "^6.1.0" + webidl-conversions "^7.0.0" + whatwg-encoding "^2.0.0" + whatwg-mimetype "^3.0.0" has-bigints@^1.0.2: version "1.0.2" @@ -4758,56 +2823,16 @@ has-tostringtag@^1.0.0: dependencies: has-symbols "^1.0.2" -has-unicode@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" - -has-value@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" - dependencies: - get-value "^2.0.3" - has-values "^0.1.4" - isobject "^2.0.0" - -has-value@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177" - dependencies: - get-value "^2.0.6" - has-values "^1.0.0" - isobject "^3.0.0" - -has-values@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771" - -has-values@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f" - dependencies: - is-number "^3.0.0" - kind-of "^4.0.0" - has@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" dependencies: function-bind "^1.1.1" -hash-base@^3.0.0: - version "3.0.4" - resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.0.4.tgz#5fc8686847ecd73499403319a6b0a3f3f6ae4918" - dependencies: - inherits "^2.0.1" - safe-buffer "^5.0.1" - -hash.js@^1.0.0, hash.js@^1.0.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.3.tgz#340dedbe6290187151c1ea1d777a3448935df846" - dependencies: - inherits "^2.0.3" - minimalistic-assert "^1.0.0" +he@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" + integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== hex-color-regex@^1.1.0: version "1.1.0" @@ -4819,52 +2844,13 @@ hey-listen@^1.0.8: resolved "https://registry.yarnpkg.com/hey-listen/-/hey-listen-1.0.8.tgz#8e59561ff724908de1aa924ed6ecc84a56a9aa68" integrity sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q== -history@^4.9.0: - version "4.9.0" - resolved "https://registry.yarnpkg.com/history/-/history-4.9.0.tgz#84587c2068039ead8af769e9d6a6860a14fa1bca" - dependencies: - "@babel/runtime" "^7.1.2" - loose-envify "^1.2.0" - resolve-pathname "^2.2.0" - tiny-invariant "^1.0.2" - tiny-warning "^1.0.0" - value-equal "^0.4.0" - -hmac-drbg@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" - dependencies: - hash.js "^1.0.3" - minimalistic-assert "^1.0.0" - minimalistic-crypto-utils "^1.0.1" - -hoist-non-react-statics@^3.0.0, hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0: +hoist-non-react-statics@^3.0.0, hoist-non-react-statics@^3.3.0: version "3.3.2" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== dependencies: react-is "^16.7.0" -homedir-polyfill@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz#743298cef4e5af3e194161fbadcc2151d3a058e8" - dependencies: - parse-passwd "^1.0.0" - -hoopy@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/hoopy/-/hoopy-0.1.4.tgz#609207d661100033a9a9402ad3dea677381c1b1d" - integrity sha512-HRcs+2mr52W0K+x8RzcLzuPPmVIKMSv97RGHy0Ea9y/mpcaK+xTrjICA04KAHi4GRzxliNqNJEFYWHghy3rSfQ== - -hpack.js@^2.1.6: - version "2.1.6" - resolved "https://registry.yarnpkg.com/hpack.js/-/hpack.js-2.1.6.tgz#87774c0949e513f42e84575b3c45681fade2a0b2" - dependencies: - inherits "^2.0.1" - obuf "^1.0.0" - readable-stream "^2.0.1" - wbuf "^1.1.0" - hsl-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/hsl-regex/-/hsl-regex-1.0.0.tgz#d49330c789ed819e276a4c0d272dffa30b18fe6e" @@ -4875,157 +2861,70 @@ hsla-regex@^1.0.0: resolved "https://registry.yarnpkg.com/hsla-regex/-/hsla-regex-1.0.0.tgz#c1ce7a3168c8c6614033a4b5f7877f3b225f9c38" integrity sha512-7Wn5GMLuHBjZCb2bTmnDOycho0p/7UVaAeqXZGbHrBCl6Yd/xDhQJAXe6Ga9AXJH2I5zY1dEdYw2u1UptnSBJA== -html-entities@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.3.1.tgz#fb9a1a4b5b14c5daba82d3e34c6ae4fe701a0e44" - integrity sha512-rhE/4Z3hIhzHAUKbW8jVcCyuT5oJCXXqhN/6mXXVCpzTmvJnoH2HL/bt3EZ6p55jbFJBeAe1ZNpL5BugLujxNA== - -html-escaper@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" - integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== - -html-parse-stringify2@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/html-parse-stringify2/-/html-parse-stringify2-2.0.1.tgz#dc5670b7292ca158b7bc916c9a6735ac8872834a" +html-parse-stringify@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz#dfc1017347ce9f77c8141a507f233040c59c55d2" + integrity sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg== dependencies: - void-elements "^2.0.1" + void-elements "3.1.0" html-tags@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-3.1.0.tgz#7b5e6f7e665e9fb41f30007ed9e0d41e97fb2140" integrity sha512-1qYz89hW3lFDEazhjW0yVAV87lw8lVkrJocr72XmBkMKsoSVJCQx3W8BXsC7hO2qAt8BoVjYjtAcZ9perqGnNg== -http-deceiver@^1.2.7: - version "1.2.7" - resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" - -http-errors@1.7.2: - version "1.7.2" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f" - dependencies: - depd "~1.1.2" - inherits "2.0.3" - setprototypeof "1.1.1" - statuses ">= 1.5.0 < 2" - toidentifier "1.0.0" - -http-errors@~1.6.2: - version "1.6.3" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" +http-basic@^8.1.1: + version "8.1.3" + resolved "https://registry.yarnpkg.com/http-basic/-/http-basic-8.1.3.tgz#a7cabee7526869b9b710136970805b1004261bbf" + integrity sha512-/EcDMwJZh3mABI2NhGfHOGOeOZITqfkEO4p/xK+l3NpyncIHUQBoMvCSF/b5GqvKtySC2srL/GGG3+EtlqlmCw== dependencies: - depd "~1.1.2" - inherits "2.0.3" - setprototypeof "1.1.0" - statuses ">= 1.4.0 < 2" - -http-errors@~1.7.2: - version "1.7.3" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06" - dependencies: - depd "~1.1.2" - inherits "2.0.4" - setprototypeof "1.1.1" - statuses ">= 1.5.0 < 2" - toidentifier "1.0.0" - -http-parser-js@>=0.4.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.0.tgz#d65edbede84349d0dc30320815a15d39cc3cbbd8" - -http-proxy-middleware@0.19.1: - version "0.19.1" - resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz#183c7dc4aa1479150306498c210cdaf96080a43a" - dependencies: - http-proxy "^1.17.0" - is-glob "^4.0.0" - lodash "^4.17.11" - micromatch "^3.1.10" + caseless "^0.12.0" + concat-stream "^1.6.2" + http-response-object "^3.0.1" + parse-cache-control "^1.0.1" -http-proxy@^1.17.0: - version "1.18.0" - resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.0.tgz#dbe55f63e75a347db7f3d99974f2692a314a6a3a" +http-response-object@^3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/http-response-object/-/http-response-object-3.0.2.tgz#7f435bb210454e4360d074ef1f989d5ea8aa9810" + integrity sha512-bqX0XTF6fnXSQcEJ2Iuyr75yVakyjIDCqroJQ/aHfSdlM743Cwqoi2nDYMzLGWUcuTWGWy8AAvOKXTfiv6q9RA== dependencies: - eventemitter3 "^4.0.0" - follow-redirects "^1.0.0" - requires-port "^1.0.0" - -https-browserify@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" - -human-signals@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" - integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== + "@types/node" "^10.0.3" -i18next-http-backend@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/i18next-http-backend/-/i18next-http-backend-1.4.1.tgz#d8d308e7d8c5b89988446d0b83f469361e051bc0" - integrity sha512-s4Q9hK2jS29iyhniMP82z+yYY8riGTrWbnyvsSzi5TaF7Le4E7b5deTmtuaRuab9fdDcYXtcwdBgawZG+JCEjA== +i18next-http-backend@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/i18next-http-backend/-/i18next-http-backend-2.0.0.tgz#7be736eb4c592e110b9ee54a985b737248d1c43f" + integrity sha512-6aFT5LcDOSxFyaoezruIxZDzpp6nu92j1iZc444nrz/OOaF7rsxQFNi1es19la53MQQFzG7uD2Koxi7Jav8khg== dependencies: cross-fetch "3.1.5" -i18next-multiload-backend-adapter@^1.0.0: +i18next-multiload-backend-adapter@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/i18next-multiload-backend-adapter/-/i18next-multiload-backend-adapter-1.0.0.tgz#3cc3ea102814273bb9059a317d04a3b6e4316121" integrity sha512-rZd/Qmr7KkGktVgJa78GPLXEnd51OyB2I9qmbI/mXKPm3MWbXwplIApqmZgxkPC9ce+b8Jnk227qX62W9SaLPQ== -i18next@^21.8.9: - version "21.8.9" - resolved "https://registry.yarnpkg.com/i18next/-/i18next-21.8.9.tgz#c79edd5bba61e0a0d5b43a93d52e2d13a526de82" - integrity sha512-PY9a/8ADVmnju1tETeglbbVQi+nM5pcJQWm9kvKMTE3GPgHHtpDsHy5HQ/hccz2/xtW7j3vuso23JdQSH0EttA== +i18next@22.0.3: + version "22.0.3" + resolved "https://registry.yarnpkg.com/i18next/-/i18next-22.0.3.tgz#084e40ec88d63c13385175ddebcc4395c89b97e3" + integrity sha512-gG6kCG5+gnPXdK8TLTJ2oiuFSjn6CYMSUwV3vnmISxwTunJHREn/z6gi1g7942c61K1dL3Gm+9a64nZCPv6mlg== dependencies: "@babel/runtime" "^7.17.2" -iconv-lite@0.4.24, iconv-lite@^0.4.4: - version "0.4.24" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" - dependencies: - safer-buffer ">= 2.1.2 < 3" - -iconv-lite@^0.6.2: +iconv-lite@0.6.3: version "0.6.3" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== dependencies: safer-buffer ">= 2.1.2 < 3.0.0" -icss-utils@^5.0.0, icss-utils@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae" - integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA== - -identity-obj-proxy@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/identity-obj-proxy/-/identity-obj-proxy-3.0.0.tgz#94d2bda96084453ef36fbc5aaec37e0f79f1fc14" - integrity sha512-00n6YnVHKrinT9t0d9+5yZC6UBNJANpYEQvL2LlX6Ab9lnmxzIRcEmTPuyGScvl1+jKuCICX1Z0Ab1pPKKdikA== - dependencies: - harmony-reflect "^1.4.6" - -ieee754@^1.1.4: - version "1.1.11" - resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.11.tgz#c16384ffe00f5b7835824e67b6f2bd44a5229455" - -iferr@^0.1.5: - version "0.1.5" - resolved "https://registry.yarnpkg.com/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501" - -ignore-walk@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.1.tgz#a83e62e7d272ac0e3b551aaa82831a19b69f82f8" - dependencies: - minimatch "^3.0.4" - ignore@^5.1.1, ignore@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== -immer@7.0.9: - version "7.0.9" - resolved "https://registry.yarnpkg.com/immer/-/immer-7.0.9.tgz#28e7552c21d39dd76feccd2b800b7bc86ee4a62e" - integrity sha512-Vs/gxoM4DqNAYR7pugIxi0Xc8XAun/uy7AQu4fLLqaTBHxjOP9pJ266Q9MWA/ly4z6rAFZbvViOtihxUZ7O28A== +immer@^9.0.12: + version "9.0.16" + resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.16.tgz#8e7caab80118c2b54b37ad43e05758cdefad0198" + integrity sha512-qenGE7CstVm1NrHQbMh8YaSzTZTFNP3zPqr3YU0S0UY441j4bJTg4A2Hh5KAhwgaiU6ZZ1Ar6y/2f4TblnMReQ== import-fresh@^3.0.0, import-fresh@^3.1.0, import-fresh@^3.2.1: version "3.3.0" @@ -5035,35 +2934,10 @@ import-fresh@^3.0.0, import-fresh@^3.1.0, import-fresh@^3.2.1: parent-module "^1.0.0" resolve-from "^4.0.0" -import-local@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/import-local/-/import-local-2.0.0.tgz#55070be38a5993cf18ef6db7e961f5bee5c5a09d" - dependencies: - pkg-dir "^3.0.0" - resolve-cwd "^2.0.0" - -import-local@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.1.0.tgz#b4479df8a5fd44f6cdce24070675676063c95cb4" - integrity sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg== - dependencies: - pkg-dir "^4.2.0" - resolve-cwd "^3.0.0" - imurmurhash@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" -indent-string@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" - integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== - -infer-owner@^1.0.3, infer-owner@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/infer-owner/-/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467" - integrity sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A== - inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" @@ -5072,29 +2946,10 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3: +inherits@2, inherits@^2.0.3, inherits@~2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" -inherits@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" - -inherits@2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" - -ini@^1.3.4, ini@^1.3.5, ini@~1.3.0: - version "1.3.5" - resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" - -internal-ip@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/internal-ip/-/internal-ip-4.3.0.tgz#845452baad9d2ca3b69c635a137acb9a0dad0907" - dependencies: - default-gateway "^4.2.0" - ipaddr.js "^1.9.0" - internal-slot@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.3.tgz#7347e307deeea2faac2ac6205d4bc7d34967f59c" @@ -5104,43 +2959,6 @@ internal-slot@^1.0.3: has "^1.0.3" side-channel "^1.0.4" -interpret@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e" - integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA== - -ip-regex@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9" - -ip@^1.1.0, ip@^1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" - -ipaddr.js@1.9.0: - version "1.9.0" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.0.tgz#37df74e430a0e47550fe54a2defe30d8acd95f65" - -ipaddr.js@^1.9.0: - version "1.9.1" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" - -is-absolute-url@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-3.0.3.tgz#96c6a22b6a23929b11ea0afb1836c36ad4a5d698" - -is-accessor-descriptor@^0.1.6: - version "0.1.6" - resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" - dependencies: - kind-of "^3.0.2" - -is-accessor-descriptor@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz#169c2f6d3df1f992618072365c9b0ea1f6878656" - dependencies: - kind-of "^6.0.0" - is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" @@ -5155,12 +2973,6 @@ is-bigint@^1.0.1: resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.2.tgz#ffb381442503235ad245ea89e45b3dbff040ee5a" integrity sha512-0JV5+SOCQkIdzjBK9buARcV804Ddu7A0Qet6sHi3FimE9ne6m4BGQZfRn+NZiXbBk4F4XmHfDZIipLj9pX8dSA== -is-binary-path@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898" - dependencies: - binary-extensions "^1.0.0" - is-binary-path@~2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" @@ -5175,10 +2987,6 @@ is-boolean-object@^1.1.0: dependencies: call-bind "^1.0.2" -is-buffer@^1.1.5: - version "1.1.6" - resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" - is-callable@^1.1.4, is-callable@^1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.4.tgz#47301d58dd0259407865547853df6d61fe471945" @@ -5203,79 +3011,22 @@ is-core-module@^2.2.0, is-core-module@^2.8.1: dependencies: has "^1.0.3" -is-data-descriptor@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" - dependencies: - kind-of "^3.0.2" - -is-data-descriptor@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz#d84876321d0e7add03990406abbbbd36ba9268c7" +is-core-module@^2.9.0: + version "2.11.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.11.0.tgz#ad4cb3e3863e814523c96f3f58d26cc570ff0144" + integrity sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw== dependencies: - kind-of "^6.0.0" + has "^1.0.3" is-date-object@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.1.tgz#9aa20eb6aeebbff77fbd33e74ca01b33581d3a16" -is-descriptor@^0.1.0: - version "0.1.6" - resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" - dependencies: - is-accessor-descriptor "^0.1.6" - is-data-descriptor "^0.1.4" - kind-of "^5.0.0" - -is-descriptor@^1.0.0, is-descriptor@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec" - dependencies: - is-accessor-descriptor "^1.0.0" - is-data-descriptor "^1.0.0" - kind-of "^6.0.2" - -is-extendable@^0.1.0, is-extendable@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" - -is-extendable@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4" - dependencies: - is-plain-object "^2.0.4" - -is-extglob@^2.1.0, is-extglob@^2.1.1: +is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== -is-fullwidth-code-point@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" - dependencies: - number-is-nan "^1.0.0" - -is-fullwidth-code-point@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" - -is-fullwidth-code-point@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" - integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== - -is-generator-fn@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" - integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== - -is-glob@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a" - dependencies: - is-extglob "^2.1.0" - is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: version "4.0.3" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" @@ -5293,54 +3044,11 @@ is-number-object@^1.0.4: resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.5.tgz#6edfaeed7950cff19afedce9fbfca9ee6dd289eb" integrity sha512-RU0lI/n95pMoUKu9v1BZP5MBcZuNSVJkMkAG2dJqC4z2GlkGUNeH68SuHuBKBD/XFe+LHZ+f9BKkLET60Niedw== -is-number@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" - dependencies: - kind-of "^3.0.2" - -is-number@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-4.0.0.tgz#0026e37f5454d73e356dfe6564699867c6a7f0ff" - is-number@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== -is-odd@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-odd/-/is-odd-2.0.0.tgz#7646624671fd7ea558ccd9a2795182f2958f1b24" - dependencies: - is-number "^4.0.0" - -is-path-cwd@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-2.2.0.tgz#67d43b82664a7b5191fd9119127eb300048a9fdb" - -is-path-in-cwd@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-2.1.0.tgz#bfe2dca26c69f397265a4009963602935a053acb" - dependencies: - is-path-inside "^2.1.0" - -is-path-inside@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-2.1.0.tgz#7c9810587d659a40d27bcdb4d5616eab059494b2" - dependencies: - path-is-inside "^1.0.2" - -is-plain-object@^2.0.1, is-plain-object@^2.0.3, is-plain-object@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" - dependencies: - isobject "^3.0.1" - -is-plain-object@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344" - integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q== - is-regex@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" @@ -5356,15 +3064,6 @@ is-shared-array-buffer@^1.0.2: dependencies: call-bind "^1.0.2" -is-stream@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" - -is-stream@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" - integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== - is-string@^1.0.5, is-string@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd" @@ -5386,456 +3085,19 @@ is-weakref@^1.0.2: dependencies: call-bind "^1.0.2" -is-windows@^1.0.1, is-windows@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" - -is-wsl@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" - -isarray@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" - -isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: +isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" -isobject@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" - dependencies: - isarray "1.0.0" - -isobject@^3.0.0, isobject@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" - -istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz#189e7909d0a39fa5a3dfad5b03f71947770191d3" - integrity sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw== - -istanbul-lib-instrument@^5.0.4, istanbul-lib-instrument@^5.1.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.0.tgz#31d18bdd127f825dd02ea7bfdfd906f8ab840e9f" - integrity sha512-6Lthe1hqXHBNsqvgDzGO6l03XNeu3CrG4RqQ1KM9+l5+jNGpEJfIELx1NS3SEHmJQA8np/u+E4EPRKRiu6m19A== - dependencies: - "@babel/core" "^7.12.3" - "@babel/parser" "^7.14.7" - "@istanbuljs/schema" "^0.1.2" - istanbul-lib-coverage "^3.2.0" - semver "^6.3.0" - -istanbul-lib-report@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#7518fe52ea44de372f460a76b5ecda9ffb73d8a6" - integrity sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw== - dependencies: - istanbul-lib-coverage "^3.0.0" - make-dir "^3.0.0" - supports-color "^7.1.0" - -istanbul-lib-source-maps@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551" - integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw== - dependencies: - debug "^4.1.1" - istanbul-lib-coverage "^3.0.0" - source-map "^0.6.1" - -istanbul-reports@^3.1.3: - version "3.1.4" - resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.4.tgz#1b6f068ecbc6c331040aab5741991273e609e40c" - integrity sha512-r1/DshN4KSE7xWEknZLLLLDn5CJybV3nw01VTkp6D5jzLuELlcbudfj/eSQFvrKsJuTVCGnePO7ho82Nw9zzfw== - dependencies: - html-escaper "^2.0.0" - istanbul-lib-report "^3.0.0" - -jest-changed-files@^28.0.2: - version "28.0.2" - resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-28.0.2.tgz#7d7810660a5bd043af9e9cfbe4d58adb05e91531" - integrity sha512-QX9u+5I2s54ZnGoMEjiM2WeBvJR2J7w/8ZUmH2um/WLAuGAYFQcsVXY9+1YL6k0H/AGUdH8pXUAv6erDqEsvIA== - dependencies: - execa "^5.0.0" - throat "^6.0.1" - -jest-circus@^28.1.1: - version "28.1.1" - resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-28.1.1.tgz#3d27da6a974d85a466dc0cdc6ddeb58daaa57bb4" - integrity sha512-75+BBVTsL4+p2w198DQpCeyh1RdaS2lhEG87HkaFX/UG0gJExVq2skG2pT7XZEGBubNj2CytcWSPan4QEPNosw== - dependencies: - "@jest/environment" "^28.1.1" - "@jest/expect" "^28.1.1" - "@jest/test-result" "^28.1.1" - "@jest/types" "^28.1.1" - "@types/node" "*" - chalk "^4.0.0" - co "^4.6.0" - dedent "^0.7.0" - is-generator-fn "^2.0.0" - jest-each "^28.1.1" - jest-matcher-utils "^28.1.1" - jest-message-util "^28.1.1" - jest-runtime "^28.1.1" - jest-snapshot "^28.1.1" - jest-util "^28.1.1" - pretty-format "^28.1.1" - slash "^3.0.0" - stack-utils "^2.0.3" - throat "^6.0.1" - -jest-cli@^28.1.1: - version "28.1.1" - resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-28.1.1.tgz#23ddfde8940e1818585ae4a568877b33b0e51cfe" - integrity sha512-+sUfVbJqb1OjBZ0OdBbI6OWfYM1i7bSfzYy6gze1F1w3OKWq8ZTEKkZ8a7ZQPq6G/G1qMh/uKqpdWhgl11NFQQ== - dependencies: - "@jest/core" "^28.1.1" - "@jest/test-result" "^28.1.1" - "@jest/types" "^28.1.1" - chalk "^4.0.0" - exit "^0.1.2" - graceful-fs "^4.2.9" - import-local "^3.0.2" - jest-config "^28.1.1" - jest-util "^28.1.1" - jest-validate "^28.1.1" - prompts "^2.0.1" - yargs "^17.3.1" - -jest-config@^28.1.1: - version "28.1.1" - resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-28.1.1.tgz#e90b97b984f14a6c24a221859e81b258990fce2f" - integrity sha512-tASynMhS+jVV85zKvjfbJ8nUyJS/jUSYZ5KQxLUN2ZCvcQc/OmhQl2j6VEL3ezQkNofxn5pQ3SPYWPHb0unTZA== - dependencies: - "@babel/core" "^7.11.6" - "@jest/test-sequencer" "^28.1.1" - "@jest/types" "^28.1.1" - babel-jest "^28.1.1" - chalk "^4.0.0" - ci-info "^3.2.0" - deepmerge "^4.2.2" - glob "^7.1.3" - graceful-fs "^4.2.9" - jest-circus "^28.1.1" - jest-environment-node "^28.1.1" - jest-get-type "^28.0.2" - jest-regex-util "^28.0.2" - jest-resolve "^28.1.1" - jest-runner "^28.1.1" - jest-util "^28.1.1" - jest-validate "^28.1.1" - micromatch "^4.0.4" - parse-json "^5.2.0" - pretty-format "^28.1.1" - slash "^3.0.0" - strip-json-comments "^3.1.1" - -jest-diff@^28.1.1: - version "28.1.1" - resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-28.1.1.tgz#1a3eedfd81ae79810931c63a1d0f201b9120106c" - integrity sha512-/MUUxeR2fHbqHoMMiffe/Afm+U8U4olFRJ0hiVG2lZatPJcnGxx292ustVu7bULhjV65IYMxRdploAKLbcrsyg== - dependencies: - chalk "^4.0.0" - diff-sequences "^28.1.1" - jest-get-type "^28.0.2" - pretty-format "^28.1.1" - -jest-docblock@^28.1.1: - version "28.1.1" - resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-28.1.1.tgz#6f515c3bf841516d82ecd57a62eed9204c2f42a8" - integrity sha512-3wayBVNiOYx0cwAbl9rwm5kKFP8yHH3d/fkEaL02NPTkDojPtheGB7HZSFY4wzX+DxyrvhXz0KSCVksmCknCuA== - dependencies: - detect-newline "^3.0.0" - -jest-each@^28.1.1: - version "28.1.1" - resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-28.1.1.tgz#ba5238dacf4f31d9fe23ddc2c44c01e7c23885c4" - integrity sha512-A042rqh17ZvEhRceDMi784ppoXR7MWGDEKTXEZXb4svt0eShMZvijGxzKsx+yIjeE8QYmHPrnHiTSQVhN4nqaw== - dependencies: - "@jest/types" "^28.1.1" - chalk "^4.0.0" - jest-get-type "^28.0.2" - jest-util "^28.1.1" - pretty-format "^28.1.1" - -jest-environment-node@^28.1.1: - version "28.1.1" - resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-28.1.1.tgz#1c86c59003a7d319fa06ea3b1bbda6c193715c67" - integrity sha512-2aV/eeY/WNgUUJrrkDJ3cFEigjC5fqT1+fCclrY6paqJ5zVPoM//sHmfgUUp7WLYxIdbPwMiVIzejpN56MxnNA== - dependencies: - "@jest/environment" "^28.1.1" - "@jest/fake-timers" "^28.1.1" - "@jest/types" "^28.1.1" - "@types/node" "*" - jest-mock "^28.1.1" - jest-util "^28.1.1" - -jest-get-type@^28.0.2: - version "28.0.2" - resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-28.0.2.tgz#34622e628e4fdcd793d46db8a242227901fcf203" - integrity sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA== - -jest-haste-map@^28.1.1: - version "28.1.1" - resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-28.1.1.tgz#471685f1acd365a9394745bb97c8fc16289adca3" - integrity sha512-ZrRSE2o3Ezh7sb1KmeLEZRZ4mgufbrMwolcFHNRSjKZhpLa8TdooXOOFlSwoUzlbVs1t0l7upVRW2K7RWGHzbQ== - dependencies: - "@jest/types" "^28.1.1" - "@types/graceful-fs" "^4.1.3" - "@types/node" "*" - anymatch "^3.0.3" - fb-watchman "^2.0.0" - graceful-fs "^4.2.9" - jest-regex-util "^28.0.2" - jest-util "^28.1.1" - jest-worker "^28.1.1" - micromatch "^4.0.4" - walker "^1.0.8" - optionalDependencies: - fsevents "^2.3.2" - -jest-leak-detector@^28.1.1: - version "28.1.1" - resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-28.1.1.tgz#537f37afd610a4b3f4cab15e06baf60484548efb" - integrity sha512-4jvs8V8kLbAaotE+wFR7vfUGf603cwYtFf1/PYEsyX2BAjSzj8hQSVTP6OWzseTl0xL6dyHuKs2JAks7Pfubmw== - dependencies: - jest-get-type "^28.0.2" - pretty-format "^28.1.1" - -jest-matcher-utils@^28.0.0, jest-matcher-utils@^28.1.1: - version "28.1.1" - resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-28.1.1.tgz#a7c4653c2b782ec96796eb3088060720f1e29304" - integrity sha512-NPJPRWrbmR2nAJ+1nmnfcKKzSwgfaciCCrYZzVnNoxVoyusYWIjkBMNvu0RHJe7dNj4hH3uZOPZsQA+xAYWqsw== - dependencies: - chalk "^4.0.0" - jest-diff "^28.1.1" - jest-get-type "^28.0.2" - pretty-format "^28.1.1" - -jest-message-util@^28.1.1: - version "28.1.1" - resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-28.1.1.tgz#60aa0b475cfc08c8a9363ed2fb9108514dd9ab89" - integrity sha512-xoDOOT66fLfmTRiqkoLIU7v42mal/SqwDKvfmfiWAdJMSJiU+ozgluO7KbvoAgiwIrrGZsV7viETjc8GNrA/IQ== - dependencies: - "@babel/code-frame" "^7.12.13" - "@jest/types" "^28.1.1" - "@types/stack-utils" "^2.0.0" - chalk "^4.0.0" - graceful-fs "^4.2.9" - micromatch "^4.0.4" - pretty-format "^28.1.1" - slash "^3.0.0" - stack-utils "^2.0.3" - -jest-mock@^28.1.1: - version "28.1.1" - resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-28.1.1.tgz#37903d269427fa1ef5b2447be874e1c62a39a371" - integrity sha512-bDCb0FjfsmKweAvE09dZT59IMkzgN0fYBH6t5S45NoJfd2DHkS3ySG2K+hucortryhO3fVuXdlxWcbtIuV/Skw== - dependencies: - "@jest/types" "^28.1.1" - "@types/node" "*" - -jest-pnp-resolver@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz#b704ac0ae028a89108a4d040b3f919dfddc8e33c" - integrity sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w== - -jest-regex-util@^28.0.2: - version "28.0.2" - resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-28.0.2.tgz#afdc377a3b25fb6e80825adcf76c854e5bf47ead" - integrity sha512-4s0IgyNIy0y9FK+cjoVYoxamT7Zeo7MhzqRGx7YDYmaQn1wucY9rotiGkBzzcMXTtjrCAP/f7f+E0F7+fxPNdw== - -jest-resolve-dependencies@^28.1.1: - version "28.1.1" - resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-28.1.1.tgz#3dffaaa56f4b41bc6b61053899d1756401763a27" - integrity sha512-p8Y150xYJth4EXhOuB8FzmS9r8IGLEioiaetgdNGb9VHka4fl0zqWlVe4v7mSkYOuEUg2uB61iE+zySDgrOmgQ== - dependencies: - jest-regex-util "^28.0.2" - jest-snapshot "^28.1.1" - -jest-resolve@^28.1.1: - version "28.1.1" - resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-28.1.1.tgz#bc2eaf384abdcc1aaf3ba7c50d1adf01e59095e5" - integrity sha512-/d1UbyUkf9nvsgdBildLe6LAD4DalgkgZcKd0nZ8XUGPyA/7fsnaQIlKVnDiuUXv/IeZhPEDrRJubVSulxrShA== - dependencies: - chalk "^4.0.0" - graceful-fs "^4.2.9" - jest-haste-map "^28.1.1" - jest-pnp-resolver "^1.2.2" - jest-util "^28.1.1" - jest-validate "^28.1.1" - resolve "^1.20.0" - resolve.exports "^1.1.0" - slash "^3.0.0" - -jest-runner@^28.1.1: - version "28.1.1" - resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-28.1.1.tgz#9ecdb3f27a00059986797aa6b012ba8306aa436c" - integrity sha512-W5oFUiDBgTsCloTAj6q95wEvYDB0pxIhY6bc5F26OucnwBN+K58xGTGbliSMI4ChQal5eANDF+xvELaYkJxTmA== - dependencies: - "@jest/console" "^28.1.1" - "@jest/environment" "^28.1.1" - "@jest/test-result" "^28.1.1" - "@jest/transform" "^28.1.1" - "@jest/types" "^28.1.1" - "@types/node" "*" - chalk "^4.0.0" - emittery "^0.10.2" - graceful-fs "^4.2.9" - jest-docblock "^28.1.1" - jest-environment-node "^28.1.1" - jest-haste-map "^28.1.1" - jest-leak-detector "^28.1.1" - jest-message-util "^28.1.1" - jest-resolve "^28.1.1" - jest-runtime "^28.1.1" - jest-util "^28.1.1" - jest-watcher "^28.1.1" - jest-worker "^28.1.1" - source-map-support "0.5.13" - throat "^6.0.1" - -jest-runtime@^28.1.1: - version "28.1.1" - resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-28.1.1.tgz#569e1dc3c36c6c4c0b29516c1c49b6ad580abdaf" - integrity sha512-J89qEJWW0leOsqyi0D9zHpFEYHwwafFdS9xgvhFHtIdRghbadodI0eA+DrthK/1PebBv3Px8mFSMGKrtaVnleg== - dependencies: - "@jest/environment" "^28.1.1" - "@jest/fake-timers" "^28.1.1" - "@jest/globals" "^28.1.1" - "@jest/source-map" "^28.0.2" - "@jest/test-result" "^28.1.1" - "@jest/transform" "^28.1.1" - "@jest/types" "^28.1.1" - chalk "^4.0.0" - cjs-module-lexer "^1.0.0" - collect-v8-coverage "^1.0.0" - execa "^5.0.0" - glob "^7.1.3" - graceful-fs "^4.2.9" - jest-haste-map "^28.1.1" - jest-message-util "^28.1.1" - jest-mock "^28.1.1" - jest-regex-util "^28.0.2" - jest-resolve "^28.1.1" - jest-snapshot "^28.1.1" - jest-util "^28.1.1" - slash "^3.0.0" - strip-bom "^4.0.0" - -jest-snapshot@^28.1.1: - version "28.1.1" - resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-28.1.1.tgz#ab825c16c8d8b5e883bd57eee6ca8748c42ab848" - integrity sha512-1KjqHJ98adRcbIdMizjF5DipwZFbvxym/kFO4g4fVZCZRxH/dqV8TiBFCa6rqic3p0karsy8RWS1y4E07b7P0A== - dependencies: - "@babel/core" "^7.11.6" - "@babel/generator" "^7.7.2" - "@babel/plugin-syntax-typescript" "^7.7.2" - "@babel/traverse" "^7.7.2" - "@babel/types" "^7.3.3" - "@jest/expect-utils" "^28.1.1" - "@jest/transform" "^28.1.1" - "@jest/types" "^28.1.1" - "@types/babel__traverse" "^7.0.6" - "@types/prettier" "^2.1.5" - babel-preset-current-node-syntax "^1.0.0" - chalk "^4.0.0" - expect "^28.1.1" - graceful-fs "^4.2.9" - jest-diff "^28.1.1" - jest-get-type "^28.0.2" - jest-haste-map "^28.1.1" - jest-matcher-utils "^28.1.1" - jest-message-util "^28.1.1" - jest-util "^28.1.1" - natural-compare "^1.4.0" - pretty-format "^28.1.1" - semver "^7.3.5" - -jest-util@^28.0.0, jest-util@^28.1.1: - version "28.1.1" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-28.1.1.tgz#ff39e436a1aca397c0ab998db5a51ae2b7080d05" - integrity sha512-FktOu7ca1DZSyhPAxgxB6hfh2+9zMoJ7aEQA759Z6p45NuO8mWcqujH+UdHlCm/V6JTWwDztM2ITCzU1ijJAfw== - dependencies: - "@jest/types" "^28.1.1" - "@types/node" "*" - chalk "^4.0.0" - ci-info "^3.2.0" - graceful-fs "^4.2.9" - picomatch "^2.2.3" - -jest-validate@^28.1.1: - version "28.1.1" - resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-28.1.1.tgz#59b7b339b3c85b5144bd0c06ad3600f503a4acc8" - integrity sha512-Kpf6gcClqFCIZ4ti5++XemYJWUPCFUW+N2gknn+KgnDf549iLul3cBuKVe1YcWRlaF8tZV8eJCap0eECOEE3Ug== - dependencies: - "@jest/types" "^28.1.1" - camelcase "^6.2.0" - chalk "^4.0.0" - jest-get-type "^28.0.2" - leven "^3.1.0" - pretty-format "^28.1.1" - -jest-watcher@^28.1.1: - version "28.1.1" - resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-28.1.1.tgz#533597fb3bfefd52b5cd115cd916cffd237fb60c" - integrity sha512-RQIpeZ8EIJMxbQrXpJQYIIlubBnB9imEHsxxE41f54ZwcqWLysL/A0ZcdMirf+XsMn3xfphVQVV4EW0/p7i7Ug== - dependencies: - "@jest/test-result" "^28.1.1" - "@jest/types" "^28.1.1" - "@types/node" "*" - ansi-escapes "^4.2.1" - chalk "^4.0.0" - emittery "^0.10.2" - jest-util "^28.1.1" - string-length "^4.0.1" - -jest-worker@^26.5.0: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.6.2.tgz#7f72cbc4d643c365e27b9fd775f9d0eaa9c7a8ed" - integrity sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ== - dependencies: - "@types/node" "*" - merge-stream "^2.0.0" - supports-color "^7.0.0" - -jest-worker@^28.1.1: - version "28.1.1" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-28.1.1.tgz#3480c73247171dfd01eda77200f0063ab6a3bf28" - integrity sha512-Au7slXB08C6h+xbJPp7VIb6U0XX5Kc9uel/WFc6/rcTzGiaVCBRngBExSYuXSLFPULPSYU3cJ3ybS988lNFQhQ== - dependencies: - "@types/node" "*" - merge-stream "^2.0.0" - supports-color "^8.0.0" - -jest@^28.1.1: - version "28.1.1" - resolved "https://registry.yarnpkg.com/jest/-/jest-28.1.1.tgz#3c39a3a09791e16e9ef283597d24ab19a0df701e" - integrity sha512-qw9YHBnjt6TCbIDMPMpJZqf9E12rh6869iZaN08/vpOGgHJSAaLLUn6H8W3IAEuy34Ls3rct064mZLETkxJ2XA== - dependencies: - "@jest/core" "^28.1.1" - "@jest/types" "^28.1.1" - import-local "^3.0.2" - jest-cli "^28.1.1" - "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" -js-yaml@^3.13.1: - version "3.13.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" - dependencies: - argparse "^1.0.7" - esprima "^4.0.0" - js-yaml@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" @@ -5847,14 +3109,6 @@ jsesc@^2.5.1: version "2.5.1" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.1.tgz#e421a2a8e20d6b0819df28908f782526b96dd1fe" -jsesc@~0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" - -json-parse-better-errors@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" - json-parse-even-better-errors@^2.3.0: version "2.3.1" resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" @@ -5869,17 +3123,7 @@ json-stable-stringify-without-jsonify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" -json3@^3.3.2: - version "3.3.2" - resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.2.tgz#3c0434743df93e2f5c42aee7b19bcb483575f4e1" - -json5@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" - dependencies: - minimist "^1.2.0" - -json5@^2.1.2, json5@^2.2.1: +json5@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c" integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA== @@ -5901,44 +3145,12 @@ jsonfile@^6.0.1: array-includes "^3.1.2" object.assign "^4.1.2" -killable@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/killable/-/killable-1.0.1.tgz#4c8ce441187a061c7474fb87ca08e2a638194892" - -kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: - version "3.2.2" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" - dependencies: - is-buffer "^1.1.5" - -kind-of@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" +laravel-vite-plugin@0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/laravel-vite-plugin/-/laravel-vite-plugin-0.7.0.tgz#61403d3e3068fb62ea97d8d4feecf03db4a6a23f" + integrity sha512-eYViSOGqqVEjQJXkXZFX7DpI5f8vGyAZ9pSoAeM5esJGsX5NtdZmJf2UfblODYcaknXd4POo/pkg0bTRt97X4A== dependencies: - is-buffer "^1.1.5" - -kind-of@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" - -kind-of@^6.0.0, kind-of@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.2.tgz#01146b36a6218e64e58f3a8d66de5d7fc6f6d051" - -kleur@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" - integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== - -klona@^2.0.4: - version "2.0.5" - resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.5.tgz#d166574d90076395d9963aa7a928fabb8d76afbc" - integrity sha512-pJiBpiXMbt7dkzXe8Ghj/u4FfXOOa98fPW+bihOJ4SjnoijweJrNThJfd3ifXpXhREjpoF2mZVH1GfS9LV3kHQ== - -leven@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" - integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== + vite-plugin-full-reload "^1.0.1" levn@^0.4.1: version "0.4.1" @@ -5953,69 +3165,35 @@ lilconfig@^2.0.5: resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.5.tgz#19e57fd06ccc3848fd1891655b5a447092225b25" integrity sha512-xaYmXZtTHPAw5m+xLN8ab9C+3a8YmV3asNSPOATITbtwrfbwaLJj8h66H1WMIpALCkqsIzK3h7oQ+PdX+LQ9Eg== +lilconfig@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.6.tgz#32a384558bd58af3d4c6e077dd1ad1d397bc69d4" + integrity sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg== + lines-and-columns@^1.1.6: version "1.2.4" resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== -loader-runner@^2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357" - -loader-utils@^1.1.0, loader-utils@^1.2.3, loader-utils@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613" - integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA== - dependencies: - big.js "^5.2.2" - emojis-list "^3.0.0" - json5 "^1.0.1" - -loader-utils@^2.0.0, loader-utils@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.0.tgz#e4cace5b816d425a166b5f097e10cd12b36064b0" - integrity sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ== - dependencies: - big.js "^5.2.2" - emojis-list "^3.0.0" - json5 "^2.1.2" - -locate-path@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" - dependencies: - p-locate "^3.0.0" - path-exists "^3.0.0" - -locate-path@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" - integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== - dependencies: - p-locate "^4.1.0" +local-pkg@^0.4.2: + version "0.4.2" + resolved "https://registry.yarnpkg.com/local-pkg/-/local-pkg-0.4.2.tgz#13107310b77e74a0e513147a131a2ba288176c2f" + integrity sha512-mlERgSPrbxU3BP4qBqAvvwlgW4MTg78iwJdGGnv7kibKjWcJksrG3t6LB5lXI93wXRDvG4NpUgJFmTG4T6rdrg== -lodash-es@^4.17.11, lodash-es@^4.17.14: - version "4.17.15" - resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.15.tgz#21bd96839354412f23d7a10340e5eac6ee455d78" +lodash-es@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee" + integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw== lodash.flatmap@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.flatmap/-/lodash.flatmap-4.5.0.tgz#ef8cbf408f6e48268663345305c6acc0b778702e" integrity sha512-/OcpcAGWlrZyoHGeHh3cAoa6nGdX6QYtmzNP84Jqol6UEQQ2gIaU3H+0eICcjcKGl0/XF8LWOujNn9lffsnaOg== -lodash.get@^4.0, lodash.get@^4.4.2: +lodash.get@^4.4.2: version "4.4.2" resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" -lodash.has@^4.0: - version "4.5.2" - resolved "https://registry.yarnpkg.com/lodash.has/-/lodash.has-4.5.2.tgz#d19f4dc1095058cccbe2b0cdf4ee0fe4aa37c862" - -lodash.memoize@4.x: - version "4.1.2" - resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" - integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== - lodash.merge@^4.6.2: version "4.6.2" resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" @@ -6026,27 +3204,23 @@ lodash.topath@^4.5.2: resolved "https://registry.yarnpkg.com/lodash.topath/-/lodash.topath-4.5.2.tgz#3616351f3bba61994a0931989660bd03254fd009" integrity sha512-1/W4dM+35DwvE/iEd1M9ekewOSTlpFekhw9mhAtrwjVqUr83/ilQiyAvmg4tVX7Unkcfl1KC+i9WdaT4B6aQcg== -lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.21: +lodash@^4.17.11, lodash@^4.17.21: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== -loglevel@^1.6.8: - version "1.6.8" - resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.8.tgz#8a25fb75d092230ecd4457270d80b54e28011171" - integrity sha512-bsU7+gc9AJ2SqpzxwU3+1fedl8zAntbtC5XYlt3s2j1hJcn2PsXSmgN8TaLG/J1/2mod4+cE/3vNL70/c1RNCA== - -loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1, loose-envify@^1.4.0: +loose-envify@^1.1.0, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" dependencies: js-tokens "^3.0.0 || ^4.0.0" -lru-cache@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" +loupe@^2.3.1: + version "2.3.4" + resolved "https://registry.yarnpkg.com/loupe/-/loupe-2.3.4.tgz#7e0b9bffc76f148f9be769cb1321d3dcf3cb25f3" + integrity sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ== dependencies: - yallist "^3.0.2" + get-func-name "^2.0.0" lru-cache@^6.0.0: version "6.0.0" @@ -6060,122 +3234,19 @@ lz-string@^1.4.4: resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.4.4.tgz#c0d8eaf36059f705796e1e344811cf4c498d3a26" integrity sha512-0ckx7ZHRPqb0oUm8zNr+90mtf9DQB60H1wMCjBtfi62Kl3a7JbHob6gA2bC+xRvZoOL+1hzUK8jeuEIQE8svEQ== -make-dir@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" - dependencies: - pify "^4.0.1" - semver "^5.6.0" - -make-dir@^3.0.0, make-dir@^3.0.2, make-dir@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" - integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== - dependencies: - semver "^6.0.0" - -make-error@1.x: - version "1.3.6" - resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" - integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== - -makeerror@1.0.12: - version "1.0.12" - resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" - integrity sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg== - dependencies: - tmpl "1.0.5" - -map-cache@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" - -map-or-similar@^1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/map-or-similar/-/map-or-similar-1.5.0.tgz#6de2653174adfb5d9edc33c69d3e92a1b76faf08" - -map-visit@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" - dependencies: - object-visit "^1.0.0" - -md5.js@^1.3.4: - version "1.3.4" - resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.4.tgz#e9bdbde94a20a5ac18b04340fc5764d5b09d901d" - dependencies: - hash-base "^3.0.0" - inherits "^2.0.1" - -media-typer@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" - -memfs@^3.1.2: - version "3.4.7" - resolved "https://registry.yarnpkg.com/memfs/-/memfs-3.4.7.tgz#e5252ad2242a724f938cb937e3c4f7ceb1f70e5a" - integrity sha512-ygaiUSNalBX85388uskeCyhSAoOSgzBbtVCr9jA2RROssFL9Q19/ZXFqS+2Th2sr1ewNIWgFdLzLC3Yl1Zv+lw== - dependencies: - fs-monkey "^1.0.3" - -memoizerific@^1.11.3: - version "1.11.3" - resolved "https://registry.yarnpkg.com/memoizerific/-/memoizerific-1.11.3.tgz#7c87a4646444c32d75438570905f2dbd1b1a805a" - dependencies: - map-or-similar "^1.5.0" - -memory-fs@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552" - dependencies: - errno "^0.1.3" - readable-stream "^2.0.1" - -memory-fs@^0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.5.0.tgz#324c01288b88652966d161db77838720845a8e3c" - integrity sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA== +magic-string@^0.26.7: + version "0.26.7" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.26.7.tgz#caf7daf61b34e9982f8228c4527474dac8981d6f" + integrity sha512-hX9XH3ziStPoPhJxLq1syWuZMxbDvGNbVchfrdCtanC7D13888bMFow61x8axrx+GfHLtVeAx2kxL7tTGRl+Ow== dependencies: - errno "^0.1.3" - readable-stream "^2.0.1" - -merge-descriptors@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" - -merge-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" - integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + sourcemap-codec "^1.4.8" merge2@^1.3.0, merge2@^1.4.1: 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" - -micromatch@^3.0.4, micromatch@^3.1.10, micromatch@^3.1.4: - version "3.1.10" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" - dependencies: - arr-diff "^4.0.0" - array-unique "^0.3.2" - braces "^2.3.1" - define-property "^2.0.2" - extend-shallow "^3.0.2" - extglob "^2.0.4" - fragment-cache "^0.2.1" - kind-of "^6.0.2" - nanomatch "^1.2.9" - object.pick "^1.3.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.2" - -micromatch@^4.0.4: +micromatch@^4.0.4, micromatch@^4.0.5: version "4.0.5" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== @@ -6183,70 +3254,23 @@ micromatch@^4.0.4: braces "^3.0.2" picomatch "^2.3.1" -miller-rabin@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d" - dependencies: - bn.js "^4.0.0" - brorand "^1.0.1" - -mime-db@1.52.0, "mime-db@>= 1.40.0 < 2": +mime-db@1.52.0: version "1.52.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== -mime-types@^2.1.12, mime-types@~2.1.17, mime-types@~2.1.24: +mime-types@^2.1.12: version "2.1.35" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== dependencies: mime-db "1.52.0" -mime@1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" - -mime@^2.4.4: - version "2.4.4" - resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.4.tgz#bd7b91135fc6b01cde3e9bae33d659b63d8857e5" - -mimic-fn@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" - integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== - -min-document@^2.19.0: - version "2.19.0" - resolved "https://registry.yarnpkg.com/min-document/-/min-document-2.19.0.tgz#7bd282e3f5842ed295bb748cdd9f1ffa2c824685" - dependencies: - dom-walk "^0.1.0" - -min-indent@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" - integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== - -mini-create-react-context@^0.3.0: - version "0.3.2" - resolved "https://registry.yarnpkg.com/mini-create-react-context/-/mini-create-react-context-0.3.2.tgz#79fc598f283dd623da8e088b05db8cddab250189" - dependencies: - "@babel/runtime" "^7.4.0" - gud "^1.0.0" - tiny-warning "^1.0.2" - mini-svg-data-uri@^1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/mini-svg-data-uri/-/mini-svg-data-uri-1.2.3.tgz#e16baa92ad55ddaa1c2c135759129f41910bc39f" integrity sha512-zd6KCAyXgmq6FV1mR10oKXYtvmA9vRoB6xPSTUJTbFApCtkefDnYueVR1gkof3KcdLZo1Y8mjF2DFmQMIxsHNQ== -minimalistic-assert@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" - -minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" - minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" @@ -6254,184 +3278,45 @@ minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2: dependencies: brace-expansion "^1.1.7" -minimist@^1.1.1, minimist@^1.2.0, minimist@^1.2.5: +minimist@^1.1.1: version "1.2.5" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== -minipass-collect@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/minipass-collect/-/minipass-collect-1.0.2.tgz#22b813bf745dc6edba2576b940022ad6edc8c617" - integrity sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA== - dependencies: - minipass "^3.0.0" - -minipass-flush@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/minipass-flush/-/minipass-flush-1.0.5.tgz#82e7135d7e89a50ffe64610a787953c4c4cbb373" - integrity sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw== - dependencies: - minipass "^3.0.0" - -minipass-pipeline@^1.2.2: - version "1.2.4" - resolved "https://registry.yarnpkg.com/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz#68472f79711c084657c067c5c6ad93cddea8214c" - integrity sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A== - dependencies: - minipass "^3.0.0" - -minipass@^2.2.1, minipass@^2.3.3: - version "2.3.3" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.3.tgz#a7dcc8b7b833f5d368759cce544dccb55f50f233" - dependencies: - safe-buffer "^5.1.2" - yallist "^3.0.0" - -minipass@^3.0.0, minipass@^3.1.1: - version "3.1.6" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.6.tgz#3b8150aa688a711a1521af5e8779c1d3bb4f45ee" - integrity sha512-rty5kpw9/z8SX9dmxblFA6edItUmwJgMeYDZRrwlIVN27i8gysGbznJwUggw2V/FVqFSDdWy040ZPS811DYAqQ== - dependencies: - yallist "^4.0.0" - -minizlib@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.1.0.tgz#11e13658ce46bc3a70a267aac58359d1e0c29ceb" - dependencies: - minipass "^2.2.1" - -minizlib@^2.1.1: - version "2.1.2" - resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" - integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== - dependencies: - minipass "^3.0.0" - yallist "^4.0.0" - -mississippi@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/mississippi/-/mississippi-3.0.0.tgz#ea0a3291f97e0b5e8776b363d5f0a12d94c67022" - dependencies: - concat-stream "^1.5.0" - duplexify "^3.4.2" - end-of-stream "^1.1.0" - flush-write-stream "^1.0.0" - from2 "^2.1.0" - parallel-transform "^1.1.0" - pump "^3.0.0" - pumpify "^1.3.3" - stream-each "^1.1.0" - through2 "^2.0.0" - -mixin-deep@^1.2.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.1.tgz#a49e7268dce1a0d9698e45326c5626df3543d0fe" - dependencies: - for-in "^1.0.2" - is-extendable "^1.0.1" - -mkdirp@^0.5, mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@^0.5.3: - version "0.5.5" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" - integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== - dependencies: - minimist "^1.2.5" - -mkdirp@^1.0.3, mkdirp@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" - integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== +minimist@^1.2.6: + version "1.2.7" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.7.tgz#daa1c4d91f507390437c6a8bc01078e7000c4d18" + integrity sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g== modern-normalize@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/modern-normalize/-/modern-normalize-1.1.0.tgz#da8e80140d9221426bd4f725c6e11283d34f90b7" integrity sha512-2lMlY1Yc1+CUy0gw4H95uNN7vjbpoED7NNRSBHE25nWfLBdmMzFCsPshlzbxHz+gYMcBEUN8V4pU16prcdPSgA== -move-concurrently@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92" - dependencies: - aproba "^1.1.1" - copy-concurrently "^1.0.0" - fs-write-stream-atomic "^1.0.8" - mkdirp "^0.5.1" - rimraf "^2.5.4" - run-queue "^1.0.3" - -ms@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - -ms@2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" - -ms@2.1.2, ms@^2.1.1: +ms@2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -multicast-dns-service-types@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz#899f11d9686e5e05cb91b35d5f0e63b773cfc901" - -multicast-dns@^6.0.1: - version "6.2.3" - resolved "https://registry.yarnpkg.com/multicast-dns/-/multicast-dns-6.2.3.tgz#a0ec7bd9055c4282f790c3c82f4e28db3b31b229" - dependencies: - dns-packet "^1.3.1" - thunky "^1.0.2" +nanoclone@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/nanoclone/-/nanoclone-0.2.1.tgz#dd4090f8f1a110d26bb32c49ed2f5b9235209ed4" + integrity sha512-wynEP02LmIbLpcYw8uBKpcfF6dmg2vcpKqxeH5UcoKEYdExslsdUA4ugFauuaeYdTB76ez6gJW8XAZ6CgkXYxA== -nan@^2.9.2: - version "2.10.0" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.10.0.tgz#96d0cd610ebd58d4b4de9cc0c6828cda99c7548f" +nanoid@4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-4.0.0.tgz#6e144dee117609232c3f415c34b0e550e64999a5" + integrity sha512-IgBP8piMxe/gf73RTQx7hmnhwz0aaEXYakvqZyE302IXW3HyVNhdNGC+O2MwMAVhLEnvXlvKtGbtJf6wvHihCg== nanoid@^3.3.4: version "3.3.4" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab" integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw== -nanomatch@^1.2.9: - version "1.2.9" - resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.9.tgz#879f7150cb2dab7a471259066c104eee6e0fa7c2" - dependencies: - arr-diff "^4.0.0" - array-unique "^0.3.2" - define-property "^2.0.2" - extend-shallow "^3.0.2" - fragment-cache "^0.2.1" - is-odd "^2.0.0" - is-windows "^1.0.2" - kind-of "^6.0.2" - object.pick "^1.3.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" -needle@^2.2.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/needle/-/needle-2.2.1.tgz#b5e325bd3aae8c2678902fa296f729455d1d3a7d" - dependencies: - debug "^2.1.2" - iconv-lite "^0.4.4" - sax "^1.2.4" - -negotiator@0.6.2: - version "0.6.2" - resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" - -neo-async@^2.5.0, neo-async@^2.6.1: - version "2.6.1" - resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.1.tgz#ac27ada66167fa8849a6addd837f6b189ad2081c" - -nice-try@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.4.tgz#d93962f6c52f2c1558c0fbda6d512819f1efe1c4" - node-emoji@^1.11.0: version "1.11.0" resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-1.11.0.tgz#69a0150e6946e2f115e9d7ea4df7971e2628301c" @@ -6439,82 +3324,22 @@ node-emoji@^1.11.0: dependencies: lodash "^4.17.21" -node-fetch@2.6.7: +node-fetch@2.6.7, node-fetch@^2.x.x: version "2.6.7" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== dependencies: whatwg-url "^5.0.0" -node-forge@0.9.0: - version "0.9.0" - resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.9.0.tgz#d624050edbb44874adca12bb9a52ec63cb782579" - -node-int64@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" - integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== - -node-libs-browser@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.2.1.tgz#b64f513d18338625f90346d27b0d235e631f6425" - dependencies: - assert "^1.1.1" - browserify-zlib "^0.2.0" - buffer "^4.3.0" - console-browserify "^1.1.0" - constants-browserify "^1.0.0" - crypto-browserify "^3.11.0" - domain-browser "^1.1.1" - events "^3.0.0" - https-browserify "^1.0.0" - os-browserify "^0.3.0" - path-browserify "0.0.1" - process "^0.11.10" - punycode "^1.2.4" - querystring-es3 "^0.2.0" - readable-stream "^2.3.3" - stream-browserify "^2.0.1" - stream-http "^2.7.2" - string_decoder "^1.0.0" - timers-browserify "^2.0.4" - tty-browserify "0.0.0" - url "^0.11.0" - util "^0.11.0" - vm-browserify "^1.0.1" - -node-pre-gyp@^0.10.0: - version "0.10.0" - resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.10.0.tgz#6e4ef5bb5c5203c6552448828c852c40111aac46" - dependencies: - detect-libc "^1.0.2" - mkdirp "^0.5.1" - needle "^2.2.0" - nopt "^4.0.1" - npm-packlist "^1.1.6" - npmlog "^4.0.2" - rc "^1.1.7" - rimraf "^2.6.1" - semver "^5.3.0" - tar "^4" - node-releases@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.5.tgz#280ed5bc3eba0d96ce44897d8aee478bfb3d9666" integrity sha512-U9h1NLROZTq9uE1SNffn6WuPDg8icmi3ns4rEl/oTfIle4iLjTliCzgTsbaIFMq/Xn078/lfY/BL0GWZ+psK4Q== -nopt@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d" - dependencies: - abbrev "1" - osenv "^0.1.4" - -normalize-path@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" - dependencies: - remove-trailing-separator "^1.0.1" +node-releases@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.6.tgz#8a7088c63a55e493845683ebf3c828d8c51c5503" + integrity sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg== normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" @@ -6526,55 +3351,10 @@ normalize-range@^0.1.2: resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" integrity sha1-LRDAa9/TEuqXd2laTShDlFa3WUI= -npm-bundled@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.0.3.tgz#7e71703d973af3370a9591bafe3a63aca0be2308" - -npm-packlist@^1.1.6: - version "1.1.10" - resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.1.10.tgz#1039db9e985727e464df066f4cf0ab6ef85c398a" - dependencies: - ignore-walk "^3.0.1" - npm-bundled "^1.0.1" - -npm-run-path@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" - dependencies: - path-key "^2.0.0" - -npm-run-path@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" - integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== - dependencies: - path-key "^3.0.0" - -npmlog@^4.0.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" - dependencies: - are-we-there-yet "~1.1.2" - console-control-strings "~1.1.0" - gauge "~2.7.3" - set-blocking "~2.0.0" - -number-is-nan@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" - -object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: +object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" -object-copy@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" - dependencies: - copy-descriptor "^0.1.0" - define-property "^0.2.5" - kind-of "^3.0.3" - object-hash@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-2.2.0.tgz#5ad518581eefc443bd763472b8ff2e9c2c0d54a5" @@ -6594,13 +3374,7 @@ object-keys@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" -object-visit@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" - dependencies: - isobject "^3.0.0" - -object.assign@^4.1.0, object.assign@^4.1.2: +object.assign@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940" integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ== @@ -6636,12 +3410,6 @@ object.hasown@^1.1.1: define-properties "^1.1.4" es-abstract "^1.19.5" -object.pick@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" - dependencies: - isobject "^3.0.1" - object.values@^1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.5.tgz#959f63e3ce9ef108720333082131e4a459b716ac" @@ -6651,45 +3419,13 @@ object.values@^1.1.5: define-properties "^1.1.3" es-abstract "^1.19.1" -obuf@^1.0.0, obuf@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" - -on-finished@~2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" - dependencies: - ee-first "1.1.1" - -on-headers@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f" - -once@^1.3.0, once@^1.3.1, once@^1.4.0: +once@^1.3.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== dependencies: wrappy "1" -onetime@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" - integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== - dependencies: - mimic-fn "^2.1.0" - -opener@^1.5.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.1.tgz#6d2f0e77f1a0af0032aca716c2c1fbb8e7e8abed" - integrity sha512-goYSy5c2UXE4Ra1xixabeVh1guIX/ZV/YokJksb6q2lubWu6UbvPQ20p542/sFIll1nl8JnCyK9oBaOcCWXwvA== - -opn@^5.5.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/opn/-/opn-5.5.0.tgz#fc7164fab56d235904c51c3b27da6758ca3b9bfc" - dependencies: - is-wsl "^1.1.0" - optionator@^0.9.1: version "0.9.1" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" @@ -6702,95 +3438,6 @@ optionator@^0.9.1: type-check "^0.4.0" word-wrap "^1.2.3" -original@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/original/-/original-1.0.2.tgz#e442a61cffe1c5fd20a65f3261c26663b303f25f" - dependencies: - url-parse "^1.4.3" - -os-browserify@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" - -os-homedir@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" - -os-tmpdir@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" - -osenv@^0.1.4: - version "0.1.5" - resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410" - dependencies: - os-homedir "^1.0.0" - os-tmpdir "^1.0.0" - -p-finally@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" - -p-limit@^2.0.0, p-limit@^2.2.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" - integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== - dependencies: - p-try "^2.0.0" - -p-limit@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" - integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== - dependencies: - yocto-queue "^0.1.0" - -p-locate@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" - dependencies: - p-limit "^2.0.0" - -p-locate@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" - integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== - dependencies: - p-limit "^2.2.0" - -p-map@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/p-map/-/p-map-2.1.0.tgz#310928feef9c9ecc65b68b17693018a665cea175" - -p-map@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" - integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== - dependencies: - aggregate-error "^3.0.0" - -p-retry@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-3.0.1.tgz#316b4c8893e2c8dc1cfa891f406c4b422bebf328" - dependencies: - retry "^0.12.0" - -p-try@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.0.0.tgz#85080bb87c64688fa47996fe8f7dfbe8211760b1" - -pako@~1.0.5: - version "1.0.6" - resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.6.tgz#0101211baa70c4bca4a0f63f2206e97b7dfaf258" - -parallel-transform@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/parallel-transform/-/parallel-transform-1.1.0.tgz#d410f065b05da23081fcd10f28854c29bda33b06" - dependencies: - cyclist "~0.2.2" - inherits "^2.0.3" - readable-stream "^2.1.5" - parent-module@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" @@ -6798,17 +3445,12 @@ parent-module@^1.0.0: dependencies: callsites "^3.0.0" -parse-asn1@^5.0.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.1.tgz#f6bf293818332bd0dab54efb16087724745e6ca8" - dependencies: - asn1.js "^4.0.0" - browserify-aes "^1.0.0" - create-hash "^1.1.0" - evp_bytestokey "^1.0.0" - pbkdf2 "^3.0.3" +parse-cache-control@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parse-cache-control/-/parse-cache-control-1.0.1.tgz#8eeab3e54fa56920fe16ba38f77fa21aacc2d74e" + integrity sha512-60zvsJReQPX5/QP0Kzfd/VrpjScIQ7SHBW6bFCYfEP+fp0Eppr1SHhIO5nd1PjZtvclzSzES9D/p5nFJurwfWg== -parse-json@^5.0.0, parse-json@^5.2.0: +parse-json@^5.0.0: version "5.2.0" resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== @@ -6818,49 +3460,12 @@ parse-json@^5.0.0, parse-json@^5.2.0: json-parse-even-better-errors "^2.3.0" lines-and-columns "^1.1.6" -parse-passwd@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6" - -parseurl@~1.3.2, parseurl@~1.3.3: - version "1.3.3" - resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" - -pascalcase@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" - -path-browserify@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.1.tgz#e6c4ddd7ed3aa27c68a20cc4e50e1a4ee83bbc4a" - -path-dirname@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0" - -path-exists@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" - -path-exists@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" - integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== - path-is-absolute@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== -path-is-inside@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" - -path-key@^2.0.0, path-key@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" - -path-key@^3.0.0, path-key@^3.1.0: +path-key@^3.1.0: version "3.1.1" resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== @@ -6870,104 +3475,49 @@ path-parse@^1.0.6, path-parse@^1.0.7: resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== -path-to-regexp@0.1.7: - version "0.1.7" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" - -path-to-regexp@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.7.0.tgz#59fde0f435badacba103a84e9d3bc64e96b9937d" - dependencies: - isarray "0.0.1" - path-type@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== -pbkdf2@^3.0.3: - version "3.0.16" - resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.16.tgz#7404208ec6b01b62d85bf83853a8064f8d9c2a5c" - dependencies: - create-hash "^1.1.2" - create-hmac "^1.1.4" - ripemd160 "^2.0.1" - safe-buffer "^5.0.1" - sha.js "^2.4.8" +pathe@0.3.9: + version "0.3.9" + resolved "https://registry.yarnpkg.com/pathe/-/pathe-0.3.9.tgz#4baff768f37f03e3d9341502865fb93116f65191" + integrity sha512-6Y6s0vT112P3jD8dGfuS6r+lpa0qqNrLyHPOwvXMnyNTQaYiwgau2DP3aNDsR13xqtGj7rrPo+jFUATpU6/s+g== -picocolors@^1.0.0: +pathval@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.1.tgz#8534e77a77ce7ac5a2512ea21e0fdb8fcf6c3d8d" + integrity sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ== + +picocolors@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== -picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3, picomatch@^2.3.0, picomatch@^2.3.1: +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.0, picomatch@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== -pify@^2.0.0, pify@^2.3.0: +pify@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" -pify@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" - -pinkie-promise@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" - dependencies: - pinkie "^2.0.0" - -pinkie@^2.0.0: - version "2.0.4" - resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" - -pirates@^4.0.4: - version "4.0.5" - resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.5.tgz#feec352ea5c3268fb23a37c702ab1699f35a5f3b" - integrity sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ== - -pkg-dir@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-3.0.0.tgz#2749020f239ed990881b1f71210d51eb6523bea3" - dependencies: - find-up "^3.0.0" - -pkg-dir@^4.1.0, pkg-dir@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" - integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== +popmotion@11.0.5: + version "11.0.5" + resolved "https://registry.yarnpkg.com/popmotion/-/popmotion-11.0.5.tgz#8e3e014421a0ffa30ecd722564fd2558954e1f7d" + integrity sha512-la8gPM1WYeFznb/JqF4GiTkRRPZsfaj2+kCxqQgr2MJylMmIKUwBfWW8Wa5fml/8gmtlD5yI01MP1QCZPWmppA== dependencies: - find-up "^4.0.0" - -popmotion@11.0.3: - version "11.0.3" - resolved "https://registry.yarnpkg.com/popmotion/-/popmotion-11.0.3.tgz#565c5f6590bbcddab7a33a074bb2ba97e24b0cc9" - integrity sha512-Y55FLdj3UxkR7Vl3s7Qr4e9m0onSnP8W7d/xQLsoJM40vs6UKHFdygs6SWryasTZYqugMjm3BepCF4CWXDiHgA== - dependencies: - framesync "6.0.1" + framesync "6.1.2" hey-listen "^1.0.8" - style-value-types "5.0.0" - tslib "^2.1.0" + style-value-types "5.1.2" + tslib "2.4.0" -portfinder@^1.0.26: - version "1.0.26" - resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.26.tgz#475658d56ca30bed72ac7f1378ed350bd1b64e70" - integrity sha512-Xi7mKxJHHMI3rIUrnm/jjUgwhbYMkp/XKEcZX3aG4BrumLpq3nmoQMX+ClYnDZnZ/New7IatC1no5RX0zo1vXQ== - dependencies: - async "^2.6.2" - debug "^3.1.1" - mkdirp "^0.5.1" - -posix-character-classes@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" - -postcss-attribute-case-insensitive@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-5.0.1.tgz#86d323c77ab8896ed90500071c2c8329fba64fda" - integrity sha512-wrt2VndqSLJpyBRNz9OmJcgnhI9MaongeWgapdBuUMu2a/KNJ8SENesG4SdiTnQwGO9b1VKbTWYAfCPeokLqZQ== +postcss-attribute-case-insensitive@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-5.0.2.tgz#03d761b24afc04c09e757e92ff53716ae8ea2741" + integrity sha512-XIidXV8fDr0kKt28vqki84fRK8VW8eTuIa4PChv2MqKuT6C9UjmSKzen6KaWhWEoYvwxFCa7n/tC1SZ3tyq4SQ== dependencies: postcss-selector-parser "^6.0.10" @@ -6978,59 +3528,59 @@ postcss-clamp@^4.1.0: dependencies: postcss-value-parser "^4.2.0" -postcss-color-functional-notation@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/postcss-color-functional-notation/-/postcss-color-functional-notation-4.2.3.tgz#23c9d73c76113b75473edcf66f443c6f1872bd0f" - integrity sha512-5fbr6FzFzjwHXKsVnkmEYrJYG8VNNzvD1tAXaPPWR97S6rhKI5uh2yOfV5TAzhDkZoq4h+chxEplFDc8GeyFtw== +postcss-color-functional-notation@^4.2.4: + version "4.2.4" + resolved "https://registry.yarnpkg.com/postcss-color-functional-notation/-/postcss-color-functional-notation-4.2.4.tgz#21a909e8d7454d3612d1659e471ce4696f28caec" + integrity sha512-2yrTAUZUab9s6CpxkxC4rVgFEVaR6/2Pipvi6qcgvnYiVqZcbDHEoBDhrXzyb7Efh2CCfHQNtcqWcIruDTIUeg== dependencies: postcss-value-parser "^4.2.0" -postcss-color-hex-alpha@^8.0.3: - version "8.0.3" - resolved "https://registry.yarnpkg.com/postcss-color-hex-alpha/-/postcss-color-hex-alpha-8.0.3.tgz#61a0fd151d28b128aa6a8a21a2dad24eebb34d52" - integrity sha512-fESawWJCrBV035DcbKRPAVmy21LpoyiXdPTuHUfWJ14ZRjY7Y7PA6P4g8z6LQGYhU1WAxkTxjIjurXzoe68Glw== +postcss-color-hex-alpha@^8.0.4: + version "8.0.4" + resolved "https://registry.yarnpkg.com/postcss-color-hex-alpha/-/postcss-color-hex-alpha-8.0.4.tgz#c66e2980f2fbc1a63f5b079663340ce8b55f25a5" + integrity sha512-nLo2DCRC9eE4w2JmuKgVA3fGL3d01kGq752pVALF68qpGLmx2Qrk91QTKkdUqqp45T1K1XV8IhQpcu1hoAQflQ== dependencies: postcss-value-parser "^4.2.0" -postcss-color-rebeccapurple@^7.0.2: - version "7.0.2" - resolved "https://registry.yarnpkg.com/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-7.0.2.tgz#5d397039424a58a9ca628762eb0b88a61a66e079" - integrity sha512-SFc3MaocHaQ6k3oZaFwH8io6MdypkUtEy/eXzXEB1vEQlO3S3oDc/FSZA8AsS04Z25RirQhlDlHLh3dn7XewWw== +postcss-color-rebeccapurple@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-7.1.1.tgz#63fdab91d878ebc4dd4b7c02619a0c3d6a56ced0" + integrity sha512-pGxkuVEInwLHgkNxUc4sdg4g3py7zUeCQ9sMfwyHAT+Ezk8a4OaaVZ8lIY5+oNqA/BXXgLyXv0+5wHP68R79hg== dependencies: postcss-value-parser "^4.2.0" -postcss-custom-media@^8.0.1: +postcss-custom-media@^8.0.2: version "8.0.2" resolved "https://registry.yarnpkg.com/postcss-custom-media/-/postcss-custom-media-8.0.2.tgz#c8f9637edf45fef761b014c024cee013f80529ea" integrity sha512-7yi25vDAoHAkbhAzX9dHx2yc6ntS4jQvejrNcC+csQJAXjj15e7VcWfMgLqBNAbOvqi5uIa9huOVwdHbf+sKqg== dependencies: postcss-value-parser "^4.2.0" -postcss-custom-properties@^12.1.7: - version "12.1.7" - resolved "https://registry.yarnpkg.com/postcss-custom-properties/-/postcss-custom-properties-12.1.7.tgz#ca470fd4bbac5a87fd868636dafc084bc2a78b41" - integrity sha512-N/hYP5gSoFhaqxi2DPCmvto/ZcRDVjE3T1LiAMzc/bg53hvhcHOLpXOHb526LzBBp5ZlAUhkuot/bfpmpgStJg== +postcss-custom-properties@^12.1.9: + version "12.1.10" + resolved "https://registry.yarnpkg.com/postcss-custom-properties/-/postcss-custom-properties-12.1.10.tgz#624517179fd4cf50078a7a60f628d5782e7d4903" + integrity sha512-U3BHdgrYhCrwTVcByFHs9EOBoqcKq4Lf3kXwbTi4hhq0qWhl/pDWq2THbv/ICX/Fl9KqeHBb8OVrTf2OaYF07A== dependencies: postcss-value-parser "^4.2.0" -postcss-custom-selectors@^6.0.2: +postcss-custom-selectors@^6.0.3: version "6.0.3" resolved "https://registry.yarnpkg.com/postcss-custom-selectors/-/postcss-custom-selectors-6.0.3.tgz#1ab4684d65f30fed175520f82d223db0337239d9" integrity sha512-fgVkmyiWDwmD3JbpCmB45SvvlCD6z9CG6Ie6Iere22W5aHea6oWa7EM2bpnv2Fj3I94L3VbtvX9KqwSi5aFzSg== dependencies: postcss-selector-parser "^6.0.4" -postcss-dir-pseudo-class@^6.0.4: - version "6.0.4" - resolved "https://registry.yarnpkg.com/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-6.0.4.tgz#9afe49ea631f0cb36fa0076e7c2feb4e7e3f049c" - integrity sha512-I8epwGy5ftdzNWEYok9VjW9whC4xnelAtbajGv4adql4FIF09rnrxnA9Y8xSHN47y7gqFIv10C5+ImsLeJpKBw== +postcss-dir-pseudo-class@^6.0.5: + version "6.0.5" + resolved "https://registry.yarnpkg.com/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-6.0.5.tgz#2bf31de5de76added44e0a25ecf60ae9f7c7c26c" + integrity sha512-eqn4m70P031PF7ZQIvSgy9RSJ5uI2171O/OO/zcRNYpJbvaeKFUlar1aJ7rmgiQtbm0FSPsRewjpdS0Oew7MPA== dependencies: - postcss-selector-parser "^6.0.9" + postcss-selector-parser "^6.0.10" -postcss-double-position-gradients@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/postcss-double-position-gradients/-/postcss-double-position-gradients-3.1.1.tgz#a12cfdb7d11fa1a99ccecc747f0c19718fb37152" - integrity sha512-jM+CGkTs4FcG53sMPjrrGE0rIvLDdCrqMzgDC5fLI7JHDO7o6QG8C5TQBtExb13hdBdoH9C2QVbG4jo2y9lErQ== +postcss-double-position-gradients@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/postcss-double-position-gradients/-/postcss-double-position-gradients-3.1.2.tgz#b96318fdb477be95997e86edd29c6e3557a49b91" + integrity sha512-GX+FuE/uBR6eskOK+4vkXgT6pDkexLokPaz/AbJna9s5Kzp/yl488pKPjhy0obB475ovfT1Wv8ho7U/cHNaRgQ== dependencies: "@csstools/postcss-progressive-custom-properties" "^1.1.0" postcss-value-parser "^4.2.0" @@ -7061,18 +3611,27 @@ postcss-font-variant@^5.0.0: resolved "https://registry.yarnpkg.com/postcss-font-variant/-/postcss-font-variant-5.0.0.tgz#efd59b4b7ea8bb06127f2d031bfbb7f24d32fa66" integrity sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA== -postcss-gap-properties@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/postcss-gap-properties/-/postcss-gap-properties-3.0.3.tgz#6401bb2f67d9cf255d677042928a70a915e6ba60" - integrity sha512-rPPZRLPmEKgLk/KlXMqRaNkYTUpE7YC+bOIQFN5xcu1Vp11Y4faIXv6/Jpft6FMnl6YRxZqDZG0qQOW80stzxQ== +postcss-gap-properties@^3.0.5: + version "3.0.5" + resolved "https://registry.yarnpkg.com/postcss-gap-properties/-/postcss-gap-properties-3.0.5.tgz#f7e3cddcf73ee19e94ccf7cb77773f9560aa2fff" + integrity sha512-IuE6gKSdoUNcvkGIqdtjtcMtZIFyXZhmFd5RUlg97iVEvp1BZKV5ngsAjCjrVy+14uhGBQl9tzmi1Qwq4kqVOg== -postcss-image-set-function@^4.0.6: - version "4.0.6" - resolved "https://registry.yarnpkg.com/postcss-image-set-function/-/postcss-image-set-function-4.0.6.tgz#bcff2794efae778c09441498f40e0c77374870a9" - integrity sha512-KfdC6vg53GC+vPd2+HYzsZ6obmPqOk6HY09kttU19+Gj1nC3S3XBVEXDHxkhxTohgZqzbUb94bKXvKDnYWBm/A== +postcss-image-set-function@^4.0.7: + version "4.0.7" + resolved "https://registry.yarnpkg.com/postcss-image-set-function/-/postcss-image-set-function-4.0.7.tgz#08353bd756f1cbfb3b6e93182c7829879114481f" + integrity sha512-9T2r9rsvYzm5ndsBE8WgtrMlIT7VbtTfE7b3BQnudUqnBcBo7L758oc+o+pdj/dUV0l5wjwSdjeOH2DZtfv8qw== dependencies: postcss-value-parser "^4.2.0" +postcss-import@15.0.0: + version "15.0.0" + resolved "https://registry.yarnpkg.com/postcss-import/-/postcss-import-15.0.0.tgz#0b66c25fdd9c0d19576e63c803cf39e4bad08822" + integrity sha512-Y20shPQ07RitgBGv2zvkEAu9bqvrD77C9axhj/aA1BQj4czape2MdClCExvB27EwYEJdGgKZBpKanb0t1rK2Kg== + dependencies: + postcss-value-parser "^4.0.0" + read-cache "^1.0.0" + resolve "^1.1.7" + postcss-import@^14.1.0: version "14.1.0" resolved "https://registry.yarnpkg.com/postcss-import/-/postcss-import-14.1.0.tgz#a7333ffe32f0b8795303ee9e40215dac922781f0" @@ -7102,10 +3661,10 @@ postcss-js@^4.0.0: dependencies: camelcase-css "^2.0.1" -postcss-lab-function@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/postcss-lab-function/-/postcss-lab-function-4.2.0.tgz#e054e662c6480202f5760887ec1ae0d153357123" - integrity sha512-Zb1EO9DGYfa3CP8LhINHCcTTCTLI+R3t7AX2mKsDzdgVQ/GkCpHOTgOr6HBHslP7XDdVbqgHW5vvRPMdVANQ8w== +postcss-lab-function@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/postcss-lab-function/-/postcss-lab-function-4.2.1.tgz#6fe4c015102ff7cd27d1bd5385582f67ebdbdc98" + integrity sha512-xuXll4isR03CrQsmxyz92LJB2xX9n+pZJ5jE9JgcnmsCammLyKdlzrBin+25dy6wIjfhJpKBAN80gsTlCgRk2w== dependencies: "@csstools/postcss-progressive-custom-properties" "^1.1.0" postcss-value-parser "^4.2.0" @@ -7118,17 +3677,6 @@ postcss-load-config@^3.1.0, postcss-load-config@^3.1.4: lilconfig "^2.0.5" yaml "^1.10.2" -postcss-loader@^4.0.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-4.3.0.tgz#2c4de9657cd4f07af5ab42bd60a673004da1b8cc" - integrity sha512-M/dSoIiNDOo8Rk0mUqoj4kpGq91gcxCfb9PoyZVdZ76/AuhxylHDYZblNE8o+EQ9AMSASeMFEKxZf5aU6wlx1Q== - dependencies: - cosmiconfig "^7.0.0" - klona "^2.0.4" - loader-utils "^2.0.0" - schema-utils "^3.0.0" - semver "^7.3.4" - postcss-logical@^5.0.4: version "5.0.4" resolved "https://registry.yarnpkg.com/postcss-logical/-/postcss-logical-5.0.4.tgz#ec75b1ee54421acc04d5921576b7d8db6b0e6f73" @@ -7139,34 +3687,6 @@ postcss-media-minmax@^5.0.0: resolved "https://registry.yarnpkg.com/postcss-media-minmax/-/postcss-media-minmax-5.0.0.tgz#7140bddec173e2d6d657edbd8554a55794e2a5b5" integrity sha512-yDUvFf9QdFZTuCUg0g0uNSHVlJ5X1lSzDZjPSFaiCWvjgsvu8vEVxtahPrLMinIDEEGnx6cBe6iqdx5YWz08wQ== -postcss-modules-extract-imports@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz#cda1f047c0ae80c97dbe28c3e76a43b88025741d" - integrity sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw== - -postcss-modules-local-by-default@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz#ebbb54fae1598eecfdf691a02b3ff3b390a5a51c" - integrity sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ== - dependencies: - icss-utils "^5.0.0" - postcss-selector-parser "^6.0.2" - postcss-value-parser "^4.1.0" - -postcss-modules-scope@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz#9ef3151456d3bbfa120ca44898dfca6f2fa01f06" - integrity sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg== - dependencies: - postcss-selector-parser "^6.0.4" - -postcss-modules-values@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz#d7c5e7e68c3bb3c9b27cbf48ca0bb3ffb4602c9c" - integrity sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ== - dependencies: - icss-utils "^5.0.0" - postcss-nested@5.0.6: version "5.0.6" resolved "https://registry.yarnpkg.com/postcss-nested/-/postcss-nested-5.0.6.tgz#466343f7fc8d3d46af3e7dba3fcd47d052a945bc" @@ -7174,10 +3694,17 @@ postcss-nested@5.0.6: dependencies: postcss-selector-parser "^6.0.6" -postcss-nesting@^10.1.7, postcss-nesting@^10.1.8: - version "10.1.8" - resolved "https://registry.yarnpkg.com/postcss-nesting/-/postcss-nesting-10.1.8.tgz#1675542cfedc3dc9621993f3abfdafa260c3a460" - integrity sha512-txdb3/idHYsBbNDFo1PFY0ExCgH5nfWi8G5lO49e6iuU42TydbODTzJgF5UuL5bhgeSlnAtDgfFTDG0Cl1zaSQ== +postcss-nested@6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/postcss-nested/-/postcss-nested-6.0.0.tgz#1572f1984736578f360cffc7eb7dca69e30d1735" + integrity sha512-0DkamqrPcmkBDsLn+vQDIrtkSbNkv5AD/M322ySo9kqFkCIYklym2xEmWkwo+Y3/qZo34tzEPNUw4y7yMCdv5w== + dependencies: + postcss-selector-parser "^6.0.10" + +postcss-nesting@10.2.0, postcss-nesting@^10.2.0: + version "10.2.0" + resolved "https://registry.yarnpkg.com/postcss-nesting/-/postcss-nesting-10.2.0.tgz#0b12ce0db8edfd2d8ae0aaf86427370b898890be" + integrity sha512-EwMkYchxiDiKUhlJGzWsD9b2zvq/r2SSubcRrgP+jujMXFzqvANLt16lJANC+5uZ6hjI7lpRmI6O8JIl+8l1KA== dependencies: "@csstools/selector-specificity" "^2.0.0" postcss-selector-parser "^6.0.10" @@ -7187,80 +3714,84 @@ postcss-opacity-percentage@^1.1.2: resolved "https://registry.yarnpkg.com/postcss-opacity-percentage/-/postcss-opacity-percentage-1.1.2.tgz#bd698bb3670a0a27f6d657cc16744b3ebf3b1145" integrity sha512-lyUfF7miG+yewZ8EAk9XUBIlrHyUE6fijnesuz+Mj5zrIHIEw6KcIZSOk/elVMqzLvREmXB83Zi/5QpNRYd47w== -postcss-overflow-shorthand@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/postcss-overflow-shorthand/-/postcss-overflow-shorthand-3.0.3.tgz#ebcfc0483a15bbf1b27fdd9b3c10125372f4cbc2" - integrity sha512-CxZwoWup9KXzQeeIxtgOciQ00tDtnylYIlJBBODqkgS/PU2jISuWOL/mYLHmZb9ZhZiCaNKsCRiLp22dZUtNsg== +postcss-overflow-shorthand@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/postcss-overflow-shorthand/-/postcss-overflow-shorthand-3.0.4.tgz#7ed6486fec44b76f0eab15aa4866cda5d55d893e" + integrity sha512-otYl/ylHK8Y9bcBnPLo3foYFLL6a6Ak+3EQBPOTR7luMYCOsiVTUk1iLvNf6tVPNGXcoL9Hoz37kpfriRIFb4A== + dependencies: + postcss-value-parser "^4.2.0" postcss-page-break@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/postcss-page-break/-/postcss-page-break-3.0.4.tgz#7fbf741c233621622b68d435babfb70dd8c1ee5f" integrity sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ== -postcss-place@^7.0.4: - version "7.0.4" - resolved "https://registry.yarnpkg.com/postcss-place/-/postcss-place-7.0.4.tgz#eb026650b7f769ae57ca4f938c1addd6be2f62c9" - integrity sha512-MrgKeiiu5OC/TETQO45kV3npRjOFxEHthsqGtkh3I1rPbZSbXGD/lZVi9j13cYh+NA8PIAPyk6sGjT9QbRyvSg== +postcss-place@^7.0.5: + version "7.0.5" + resolved "https://registry.yarnpkg.com/postcss-place/-/postcss-place-7.0.5.tgz#95dbf85fd9656a3a6e60e832b5809914236986c4" + integrity sha512-wR8igaZROA6Z4pv0d+bvVrvGY4GVHihBCBQieXFY3kuSuMyOmEnnfFzHl/tQuqHZkfkIVBEbDvYcFfHmpSet9g== dependencies: postcss-value-parser "^4.2.0" -postcss-preset-env@^7.7.1: - version "7.7.1" - resolved "https://registry.yarnpkg.com/postcss-preset-env/-/postcss-preset-env-7.7.1.tgz#ca416c15fd63fd44abe5dcd2890a34b0a664d2c8" - integrity sha512-1sx6+Nl1wMVJzaYLVaz4OAR6JodIN/Z1upmVqLwSPCLT6XyxrEoePgNMHPH08kseLe3z06i9Vfkt/32BYEKDeA== - dependencies: - "@csstools/postcss-cascade-layers" "^1.0.2" - "@csstools/postcss-color-function" "^1.1.0" - "@csstools/postcss-font-format-keywords" "^1.0.0" - "@csstools/postcss-hwb-function" "^1.0.1" - "@csstools/postcss-ic-unit" "^1.0.0" - "@csstools/postcss-is-pseudo-class" "^2.0.4" - "@csstools/postcss-normalize-display-values" "^1.0.0" - "@csstools/postcss-oklab-function" "^1.1.0" +postcss-preset-env@7.8.2: + version "7.8.2" + resolved "https://registry.yarnpkg.com/postcss-preset-env/-/postcss-preset-env-7.8.2.tgz#4c834d5cbd2e29df2abf59118947c456922b79ba" + integrity sha512-rSMUEaOCnovKnwc5LvBDHUDzpGP+nrUeWZGWt9M72fBvckCi45JmnJigUr4QG4zZeOHmOCNCZnd2LKDvP++ZuQ== + dependencies: + "@csstools/postcss-cascade-layers" "^1.1.0" + "@csstools/postcss-color-function" "^1.1.1" + "@csstools/postcss-font-format-keywords" "^1.0.1" + "@csstools/postcss-hwb-function" "^1.0.2" + "@csstools/postcss-ic-unit" "^1.0.1" + "@csstools/postcss-is-pseudo-class" "^2.0.7" + "@csstools/postcss-nested-calc" "^1.0.0" + "@csstools/postcss-normalize-display-values" "^1.0.1" + "@csstools/postcss-oklab-function" "^1.1.1" "@csstools/postcss-progressive-custom-properties" "^1.3.0" - "@csstools/postcss-stepped-value-functions" "^1.0.0" - "@csstools/postcss-trigonometric-functions" "^1.0.1" - "@csstools/postcss-unset-value" "^1.0.1" - autoprefixer "^10.4.7" - browserslist "^4.20.3" + "@csstools/postcss-stepped-value-functions" "^1.0.1" + "@csstools/postcss-text-decoration-shorthand" "^1.0.0" + "@csstools/postcss-trigonometric-functions" "^1.0.2" + "@csstools/postcss-unset-value" "^1.0.2" + autoprefixer "^10.4.11" + browserslist "^4.21.3" css-blank-pseudo "^3.0.3" css-has-pseudo "^3.0.4" css-prefers-color-scheme "^6.0.3" - cssdb "^6.6.3" - postcss-attribute-case-insensitive "^5.0.1" + cssdb "^7.0.1" + postcss-attribute-case-insensitive "^5.0.2" postcss-clamp "^4.1.0" - postcss-color-functional-notation "^4.2.3" - postcss-color-hex-alpha "^8.0.3" - postcss-color-rebeccapurple "^7.0.2" - postcss-custom-media "^8.0.1" - postcss-custom-properties "^12.1.7" - postcss-custom-selectors "^6.0.2" - postcss-dir-pseudo-class "^6.0.4" - postcss-double-position-gradients "^3.1.1" + postcss-color-functional-notation "^4.2.4" + postcss-color-hex-alpha "^8.0.4" + postcss-color-rebeccapurple "^7.1.1" + postcss-custom-media "^8.0.2" + postcss-custom-properties "^12.1.9" + postcss-custom-selectors "^6.0.3" + postcss-dir-pseudo-class "^6.0.5" + postcss-double-position-gradients "^3.1.2" postcss-env-function "^4.0.6" postcss-focus-visible "^6.0.4" postcss-focus-within "^5.0.4" postcss-font-variant "^5.0.0" - postcss-gap-properties "^3.0.3" - postcss-image-set-function "^4.0.6" + postcss-gap-properties "^3.0.5" + postcss-image-set-function "^4.0.7" postcss-initial "^4.0.1" - postcss-lab-function "^4.2.0" + postcss-lab-function "^4.2.1" postcss-logical "^5.0.4" postcss-media-minmax "^5.0.0" - postcss-nesting "^10.1.7" + postcss-nesting "^10.2.0" postcss-opacity-percentage "^1.1.2" - postcss-overflow-shorthand "^3.0.3" + postcss-overflow-shorthand "^3.0.4" postcss-page-break "^3.0.4" - postcss-place "^7.0.4" - postcss-pseudo-class-any-link "^7.1.4" + postcss-place "^7.0.5" + postcss-pseudo-class-any-link "^7.1.6" postcss-replace-overflow-wrap "^4.0.0" - postcss-selector-not "^6.0.0" + postcss-selector-not "^6.0.1" postcss-value-parser "^4.2.0" -postcss-pseudo-class-any-link@^7.1.4: - version "7.1.4" - resolved "https://registry.yarnpkg.com/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-7.1.4.tgz#ac72aac4fe11fc4a0a368691f8fd5fe89e95aba4" - integrity sha512-JxRcLXm96u14N3RzFavPIE9cRPuOqLDuzKeBsqi4oRk4vt8n0A7I0plFs/VXTg7U2n7g/XkQi0OwqTO3VWBfEg== +postcss-pseudo-class-any-link@^7.1.6: + version "7.1.6" + resolved "https://registry.yarnpkg.com/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-7.1.6.tgz#2693b221902da772c278def85a4d9a64b6e617ab" + integrity sha512-9sCtZkO6f/5ML9WcTLcIyV1yz9D1rf0tWc+ulKcvV30s0iZKS/ONyETvoWsr6vnrmW+X+KmuK3gV/w5EWnT37w== dependencies: postcss-selector-parser "^6.0.10" @@ -7269,14 +3800,14 @@ postcss-replace-overflow-wrap@^4.0.0: resolved "https://registry.yarnpkg.com/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-4.0.0.tgz#d2df6bed10b477bf9c52fab28c568b4b29ca4319" integrity sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw== -postcss-selector-not@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/postcss-selector-not/-/postcss-selector-not-6.0.0.tgz#d100f273d345917246762300411b4d2e24905047" - integrity sha512-i/HI/VNd3V9e1WOLCwJsf9nePBRXqcGtVibcJ9FsVo0agfDEfsLSlFt94aYjY35wUNcdG0KrvdyjEr7It50wLQ== +postcss-selector-not@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/postcss-selector-not/-/postcss-selector-not-6.0.1.tgz#8f0a709bf7d4b45222793fc34409be407537556d" + integrity sha512-1i9affjAe9xu/y9uqWH+tD4r6/hDaXJruk8xn2x1vzxC2U3J3LKO3zJW4CyxlNhA56pADJ/djpEwpH1RClI2rQ== dependencies: postcss-selector-parser "^6.0.10" -postcss-selector-parser@^6.0.10, postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4, postcss-selector-parser@^6.0.6, postcss-selector-parser@^6.0.9: +postcss-selector-parser@^6.0.10, postcss-selector-parser@^6.0.4, postcss-selector-parser@^6.0.6, postcss-selector-parser@^6.0.9: version "6.0.10" resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz#79b61e2c0d1bfc2602d549e11d0876256f8df88d" integrity sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w== @@ -7293,7 +3824,16 @@ postcss-value-parser@^4.0.0, postcss-value-parser@^4.0.2, postcss-value-parser@^ resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== -postcss@^8.1.6, postcss@^8.1.8, postcss@^8.2.15, postcss@^8.3.5, postcss@^8.4.12, postcss@^8.4.14: +postcss@8.4.18, postcss@^8.4.18: + version "8.4.18" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.18.tgz#6d50046ea7d3d66a85e0e782074e7203bc7fbca2" + integrity sha512-Wi8mWhncLJm11GATDaQKobXSNEYGUHeQLiQqDFG1qQ5UTDPTEvKw0Xt5NsTpktGTwLps3ByrWsBrG0rB8YQ9oA== + dependencies: + nanoid "^3.3.4" + picocolors "^1.0.0" + source-map-js "^1.0.2" + +postcss@^8.1.6, postcss@^8.1.8, postcss@^8.3.5: version "8.4.14" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.14.tgz#ee9274d5622b4858c1007a74d76e42e56fd21caf" integrity sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig== @@ -7314,7 +3854,7 @@ prettier-linter-helpers@^1.0.0: dependencies: fast-diff "^1.1.2" -prettier@^2.7.1: +prettier@2.7.1: version "2.7.1" resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.7.1.tgz#e235806850d057f97bb08368a4f7d899f7760c64" integrity sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g== @@ -7328,45 +3868,23 @@ pretty-format@^27.0.2: ansi-styles "^5.0.0" react-is "^17.0.1" -pretty-format@^28.0.0, pretty-format@^28.1.1: - version "28.1.1" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-28.1.1.tgz#f731530394e0f7fcd95aba6b43c50e02d86b95cb" - integrity sha512-wwJbVTGFHeucr5Jw2bQ9P+VYHyLdAqedFLEkdQUVaBF/eiidDwH5OpilINq4mEfhbCjLnirt6HTTDhv1HaTIQw== - dependencies: - "@jest/schemas" "^28.0.2" - ansi-regex "^5.0.1" - ansi-styles "^5.0.0" - react-is "^18.0.0" - pretty-hrtime@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1" process-nextick-args@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa" - -process@^0.11.10: - version "0.11.10" - resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" - -process@~0.5.1: - version "0.5.2" - resolved "https://registry.yarnpkg.com/process/-/process-0.5.2.tgz#1638d8a8e34c2f440a91db95ab9aeb677fc185cf" - -promise-inflight@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" + version "2.0.1" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== -prompts@^2.0.1: - version "2.4.2" - resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" - integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== +promise@^8.0.0: + version "8.3.0" + resolved "https://registry.yarnpkg.com/promise/-/promise-8.3.0.tgz#8cb333d1edeb61ef23869fbb8a4ea0279ab60e0a" + integrity sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg== dependencies: - kleur "^3.0.3" - sisteransi "^1.0.5" + asap "~2.0.6" -prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1: +prop-types@^15.8.1: version "15.8.1" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== @@ -7375,61 +3893,10 @@ prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2, object-assign "^4.1.1" react-is "^16.13.1" -property-expr@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/property-expr/-/property-expr-2.0.2.tgz#fff2a43919135553a3bc2fdd94bdb841965b2330" - integrity sha512-bc/5ggaYZxNkFKj374aLbEDqVADdYaLcFo8XBkishUWbaAdjlphaBFns9TvRA2pUseVL/wMFmui9X3IdNDU37g== - -proxy-addr@~2.0.5: +property-expr@^2.0.4: version "2.0.5" - resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.5.tgz#34cbd64a2d81f4b1fd21e76f9f06c8a45299ee34" - dependencies: - forwarded "~0.1.2" - ipaddr.js "1.9.0" - -prr@~1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" - -public-encrypt@^4.0.0: - version "4.0.2" - resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.2.tgz#46eb9107206bf73489f8b85b69d91334c6610994" - dependencies: - bn.js "^4.1.0" - browserify-rsa "^4.0.0" - create-hash "^1.1.0" - parse-asn1 "^5.0.0" - randombytes "^2.0.1" - -pump@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909" - dependencies: - end-of-stream "^1.1.0" - once "^1.3.1" - -pump@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" - dependencies: - end-of-stream "^1.1.0" - once "^1.3.1" - -pumpify@^1.3.3: - version "1.5.1" - resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.5.1.tgz#36513be246ab27570b1a374a5ce278bfd74370ce" - dependencies: - duplexify "^3.6.0" - inherits "^2.0.3" - pump "^2.0.0" - -punycode@1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" - -punycode@^1.2.4: - version "1.4.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" + resolved "https://registry.yarnpkg.com/property-expr/-/property-expr-2.0.5.tgz#278bdb15308ae16af3e3b9640024524f4dc02cb4" + integrity sha512-IJUkICM5dP5znhCckHSv30Q4b5/JA5enCtkRHYaOVOAocnH/1BQEYTC5NMfT3AVl/iXKdr3aqQbQn9DxyWknwA== punycode@^2.1.0: version "2.1.1" @@ -7446,35 +3913,17 @@ purgecss@^4.0.3: postcss "^8.3.5" postcss-selector-parser "^6.0.6" -qr.js@0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/qr.js/-/qr.js-0.0.0.tgz#cace86386f59a0db8050fa90d9b6b0e88a1e364f" - integrity sha1-ys6GOG9ZoNuAUPqQ2baw6IoeNk8= +qrcode.react@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/qrcode.react/-/qrcode.react-3.1.0.tgz#5c91ddc0340f768316fbdb8fff2765134c2aecd8" + integrity sha512-oyF+Urr3oAMUG/OiOuONL3HXM+53wvuH3mtIWQrYmsXoAq0DkvZp2RYUWFSMFtbdOpuS++9v+WAkzNVkMlNW6Q== -qrcode.react@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/qrcode.react/-/qrcode.react-1.0.1.tgz#2834bb50e5e275ffe5af6906eff15391fe9e38a5" - integrity sha512-8d3Tackk8IRLXTo67Y+c1rpaiXjoz/Dd2HpcMdW//62/x8J1Nbho14Kh8x974t9prsLHN6XqVgcnRiBGFptQmg== +qs@^6.4.0: + version "6.11.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" + integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q== dependencies: - loose-envify "^1.4.0" - prop-types "^15.6.0" - qr.js "0.0.0" - -qs@6.7.0: - version "6.7.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" - -querystring-es3@^0.2.0: - version "0.2.1" - resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73" - -querystring@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" - -querystringify@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.1.0.tgz#7ded8dfbf7879dcc60d0a644ac6754b283ad17ef" + side-channel "^1.0.4" queue-microtask@^1.2.2: version "1.2.3" @@ -7486,87 +3935,37 @@ quick-lru@^5.1.1: resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932" integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA== -randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5, randombytes@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" - integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== - dependencies: - safe-buffer "^5.1.0" - -randomfill@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/randomfill/-/randomfill-1.0.4.tgz#c92196fc86ab42be983f1bf31778224931d61458" - dependencies: - randombytes "^2.0.5" - safe-buffer "^5.1.0" - -range-parser@^1.2.1, range-parser@~1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" - -raw-body@2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.0.tgz#a1ce6fb9c9bc356ca52e89256ab59059e13d0332" - dependencies: - bytes "3.1.0" - http-errors "1.7.2" - iconv-lite "0.4.24" - unpipe "1.0.0" - -rc@^1.1.7: - version "1.2.8" - resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" - dependencies: - deep-extend "^0.6.0" - ini "~1.3.0" - minimist "^1.2.0" - strip-json-comments "~2.0.1" - -react-chartjs-2@^4.2.0: +react-chartjs-2@4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/react-chartjs-2/-/react-chartjs-2-4.2.0.tgz#bc5693a8b161f125301cf28ab0fe980d7dce54aa" integrity sha512-9Vm9Sg9XAKiR579/FnBkesofjW9goaaFLfS7XlGTzUJlWFZGSE6A/pBI6+i/bP3pobKZoFcWJdFnjShytToqXw== -"react-dom@npm:@hot-loader/react-dom": - version "16.11.0" - resolved "https://registry.yarnpkg.com/@hot-loader/react-dom/-/react-dom-16.11.0.tgz#c0b483923b289db5431516f56ee2a69448ebf9bd" +react-dom@18.2.0: + version "18.2.0" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d" + integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g== dependencies: loose-envify "^1.1.0" - object-assign "^4.1.1" - prop-types "^15.6.2" - scheduler "^0.17.0" + scheduler "^0.23.0" -react-fast-compare@^2.0.1: - version "2.0.4" - resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9" - -react-fast-compare@^3.2.0: +react-fast-compare@3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.0.tgz#641a9da81b6a6320f270e89724fb45a0b39e43bb" integrity sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA== -react-hot-loader@^4.12.21: - version "4.12.21" - resolved "https://registry.yarnpkg.com/react-hot-loader/-/react-hot-loader-4.12.21.tgz#332e830801fb33024b5a147d6b13417f491eb975" - integrity sha512-Ynxa6ROfWUeKWsTHxsrL2KMzujxJVPjs385lmB2t5cHUxdoRPGind9F00tOkdc1l5WBleOF4XEAMILY1KPIIDA== - dependencies: - fast-levenshtein "^2.0.6" - global "^4.3.0" - hoist-non-react-statics "^3.3.0" - loader-utils "^1.1.0" - prop-types "^15.6.1" - react-lifecycles-compat "^3.0.4" - shallowequal "^1.1.0" - source-map "^0.7.3" +react-fast-compare@^2.0.1: + version "2.0.4" + resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9" -react-i18next@^11.2.1: - version "11.2.1" - resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-11.2.1.tgz#a56d9f1f52d003eb4fa8f1c7d6752123827160f0" +react-i18next@12.0.0: + version "12.0.0" + resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-12.0.0.tgz#634015a2c035779c5736ae4c2e5c34c1659753b1" + integrity sha512-/O7N6aIEAl1FaWZBNvhdIo9itvF/MO/nRKr9pYqRc9LhuC1u21SlfwpiYQqvaeNSEW3g3qUXLREOWMt+gxrWbg== dependencies: - "@babel/runtime" "^7.3.1" - html-parse-stringify2 "2.0.1" + "@babel/runtime" "^7.14.5" + html-parse-stringify "^3.0.1" -react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0: +react-is@^16.13.1, react-is@^16.7.0: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== @@ -7576,60 +3975,32 @@ react-is@^17.0.1: resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== -react-is@^18.0.0: - version "18.2.0" - resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" - integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== - -react-lifecycles-compat@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" +react-refresh@^0.14.0: + version "0.14.0" + resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.0.tgz#4e02825378a5f227079554d4284889354e5f553e" + integrity sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ== -react-router-dom@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.1.2.tgz#06701b834352f44d37fbb6311f870f84c76b9c18" +react-router-dom@6.4.2: + version "6.4.2" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.4.2.tgz#115b37d501d6d8ac870683694978c51c43e6c0d2" + integrity sha512-yM1kjoTkpfjgczPrcyWrp+OuQMyB1WleICiiGfstnQYo/S8hPEEnVjr/RdmlH6yKK4Tnj1UGXFSa7uwAtmDoLQ== dependencies: - "@babel/runtime" "^7.1.2" - history "^4.9.0" - loose-envify "^1.3.1" - prop-types "^15.6.2" - react-router "5.1.2" - tiny-invariant "^1.0.2" - tiny-warning "^1.0.0" + "@remix-run/router" "1.0.2" + react-router "6.4.2" -react-router@5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/react-router/-/react-router-5.1.2.tgz#6ea51d789cb36a6be1ba5f7c0d48dd9e817d3418" - dependencies: - "@babel/runtime" "^7.1.2" - history "^4.9.0" - hoist-non-react-statics "^3.1.0" - loose-envify "^1.3.1" - mini-create-react-context "^0.3.0" - path-to-regexp "^1.7.0" - prop-types "^15.6.2" - react-is "^16.6.0" - tiny-invariant "^1.0.2" - tiny-warning "^1.0.0" - -react-transition-group@^4.4.1: - version "4.4.1" - resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.1.tgz#63868f9325a38ea5ee9535d828327f85773345c9" - integrity sha512-Djqr7OQ2aPUiYurhPalTrVy9ddmFCCzwhqQmtN+J3+3DzLO209Fdr70QrN8Z3DsglWql6iY1lDWAfpFiBtuKGw== +react-router@6.4.2: + version "6.4.2" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.4.2.tgz#300628ee9ed81b8ef1597b5cb98b474efe9779b8" + integrity sha512-Rb0BAX9KHhVzT1OKhMvCDMw776aTYM0DtkxqUBP8dNBom3mPXlfNs76JNGK8wKJ1IZEY1+WGj+cvZxHVk/GiKw== dependencies: - "@babel/runtime" "^7.5.5" - dom-helpers "^5.0.1" - loose-envify "^1.4.0" - prop-types "^15.6.2" + "@remix-run/router" "1.0.2" -react@^16.14.0: - version "16.14.0" - resolved "https://registry.yarnpkg.com/react/-/react-16.14.0.tgz#94d776ddd0aaa37da3eda8fc5b6b18a4c9a3114d" - integrity sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g== +react@18.2.0: + version "18.2.0" + resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5" + integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ== dependencies: loose-envify "^1.1.0" - object-assign "^4.1.1" - prop-types "^15.6.2" read-cache@^1.0.0: version "1.0.0" @@ -7638,9 +4009,10 @@ read-cache@^1.0.0: dependencies: pify "^2.3.0" -"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.6: - version "2.3.6" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" +readable-stream@^2.2.2: + version "2.3.7" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" + integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== dependencies: core-util-is "~1.0.0" inherits "~2.0.3" @@ -7650,22 +4022,6 @@ read-cache@^1.0.0: string_decoder "~1.1.1" util-deprecate "~1.0.1" -readable-stream@^3.0.6: - version "3.1.1" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.1.1.tgz#ed6bbc6c5ba58b090039ff18ce670515795aeb06" - dependencies: - inherits "^2.0.3" - string_decoder "^1.1.1" - util-deprecate "^1.0.1" - -readdirp@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525" - dependencies: - graceful-fs "^4.1.11" - micromatch "^3.1.10" - readable-stream "^2.0.2" - readdirp@~3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" @@ -7673,18 +4029,10 @@ readdirp@~3.6.0: 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" - integrity sha512-/RXiPeMd+fPUGByv+kAaQlCXCsSflZ9bKX5Fcwv9IYGS1oyT2nntL/8zn9IaiUFHL66T1jBtOABcb92g2+3w8w== - -redent@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f" - integrity sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg== - dependencies: - indent-string "^4.0.0" - strip-indent "^3.0.0" +reaptcha@1.12.1: + version "1.12.1" + resolved "https://registry.yarnpkg.com/reaptcha/-/reaptcha-1.12.1.tgz#1993d2f2ed52669188f16e3210afbf89b7221211" + integrity sha512-zoppfGKHmo8x4PBmSrIQYQOGgVp1e8wMhr6KbwAdbQ76rSky1DcDCXLWRtBg7HGXn2hw+o+0hknafMB0rnrzZQ== reduce-css-calc@^2.1.8: version "2.1.8" @@ -7694,32 +4042,17 @@ reduce-css-calc@^2.1.8: css-unit-converter "^1.1.1" postcss-value-parser "^3.3.0" -redux-devtools-extension@^2.13.8: - version "2.13.8" - resolved "https://registry.yarnpkg.com/redux-devtools-extension/-/redux-devtools-extension-2.13.8.tgz#37b982688626e5e4993ff87220c9bbb7cd2d96e1" - -redux-thunk@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.3.0.tgz#51c2c19a185ed5187aaa9a2d08b666d0d6467622" +redux-thunk@^2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.4.1.tgz#0dd8042cf47868f4b29699941de03c9301a75714" + integrity sha512-OOYGNY5Jy2TWvTL1KgAlVy6dcx3siPJ1wTq741EPyUKfn6W6nChdICjZwCd0p8AZBs5kWpZlbkXW2nE/zjUa+Q== -redux@^4.0.0, redux@^4.0.5: - version "4.0.5" - resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.5.tgz#4db5de5816e17891de8a80c424232d06f051d93f" - integrity sha512-VSz1uMAH24DM6MF72vcojpYPtrTUu3ByVWfPL1nPfVRb5mZVTve5GnNCUV53QM/BZ66xfWrm0CTWoM+Xlz8V1w== - dependencies: - loose-envify "^1.4.0" - symbol-observable "^1.2.0" - -regenerate-unicode-properties@^8.2.0: - version "8.2.0" - resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz#e5de7111d655e7ba60c057dbe9ff37c87e65cdec" - integrity sha512-F9DjY1vKLo/tPePDycuH3dn9H1OTPIkVD9Kz4LODu+F2C75mgjAJ7x/gwy6ZcSNRAAkhNlJSOHRe8k3p+K9WhA== +redux@^4.1.2: + version "4.2.0" + resolved "https://registry.yarnpkg.com/redux/-/redux-4.2.0.tgz#46f10d6e29b6666df758780437651eeb2b969f13" + integrity sha512-oSBmcKKIuIR4ME29/AeNUnl5L+hvBq7OaJWzaptTQJAntaPvxIJqfnjbaEiCzzaIz+XmVILfqAM3Ob0aXLPfjA== dependencies: - regenerate "^1.4.0" - -regenerate@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.0.tgz#4a856ec4b56e4077c557589cae85e7a4c8869a11" + "@babel/runtime" "^7.9.2" regenerator-runtime@^0.11.0: version "0.11.1" @@ -7731,20 +4064,6 @@ regenerator-runtime@^0.13.4: resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz#d878a1d094b4306d10b9096484b33ebd55e26697" integrity sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA== -regenerator-transform@^0.14.2: - version "0.14.5" - resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.14.5.tgz#c98da154683671c9c4dcb16ece736517e1b7feb4" - integrity sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw== - dependencies: - "@babel/runtime" "^7.8.4" - -regex-not@^1.0.0, regex-not@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" - dependencies: - extend-shallow "^3.0.2" - safe-regex "^1.1.0" - regexp.prototype.flags@^1.4.1, regexp.prototype.flags@^1.4.3: version "1.4.3" resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz#87cab30f80f66660181a3bb7bf5981a872b367ac" @@ -7759,112 +4078,12 @@ regexpp@^3.0.0, regexpp@^3.2.0: resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== -regexpu-core@^4.7.1: - version "4.7.1" - resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.7.1.tgz#2dea5a9a07233298fbf0db91fa9abc4c6e0f8ad6" - integrity sha512-ywH2VUraA44DZQuRKzARmw6S66mr48pQVva4LBeRhcOltJ6hExvWly5ZjFLYo67xbIxb6W1q4bAGtgfEl20zfQ== - dependencies: - regenerate "^1.4.0" - regenerate-unicode-properties "^8.2.0" - regjsgen "^0.5.1" - regjsparser "^0.6.4" - unicode-match-property-ecmascript "^1.0.4" - unicode-match-property-value-ecmascript "^1.2.0" - -regjsgen@^0.5.1: - version "0.5.2" - resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.5.2.tgz#92ff295fb1deecbf6ecdab2543d207e91aa33733" - integrity sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A== - -regjsparser@^0.6.4: - version "0.6.4" - resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.6.4.tgz#a769f8684308401a66e9b529d2436ff4d0666272" - integrity sha512-64O87/dPDgfk8/RQqC4gkZoGyyWFIEUTTh80CU6CWuK5vkCGyekIx+oKcEIYtP/RAxSQltCZHCNu/mdd7fqlJw== - dependencies: - jsesc "~0.5.0" - -remove-trailing-separator@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" - -repeat-element@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.2.tgz#ef089a178d1483baae4d93eb98b4f9e4e11d990a" - -repeat-string@^1.6.1: - version "1.6.1" - resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" - -require-directory@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" - -require-main-filename@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" - -requireindex@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/requireindex/-/requireindex-1.2.0.tgz#3463cdb22ee151902635aa6c9535d4de9c2ef1ef" - integrity sha512-L9jEkOi3ASd9PYit2cwRfyppc9NoABujTP8/5gFcbERmo5jUoAKovIC3fsF17pkTnGsrByysqX+Kxd2OTNI1ww== - -requires-port@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" - -resize-observer-polyfill@^1.5.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464" - integrity sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg== - -resolve-cwd@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a" - dependencies: - resolve-from "^3.0.0" - -resolve-cwd@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" - integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== - dependencies: - resolve-from "^5.0.0" - -resolve-dir@^1.0.0, resolve-dir@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/resolve-dir/-/resolve-dir-1.0.1.tgz#79a40644c362be82f26effe739c9bb5382046f43" - dependencies: - expand-tilde "^2.0.0" - global-modules "^1.0.0" - -resolve-from@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" - resolve-from@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== -resolve-from@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" - integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== - -resolve-pathname@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/resolve-pathname/-/resolve-pathname-2.2.0.tgz#7e9ae21ed815fd63ab189adeee64dc831eefa879" - -resolve-url@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" - -resolve.exports@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-1.1.0.tgz#5ce842b94b05146c0e03076985d1d0e7e48c90c9" - integrity sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ== - -resolve@^1.1.7, resolve@^1.10.1, resolve@^1.12.0, resolve@^1.20.0, resolve@^1.22.0, resolve@^1.8.1: +resolve@^1.1.7, resolve@^1.10.1, resolve@^1.12.0, resolve@^1.20.0: version "1.22.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.0.tgz#5e0b8c67c15df57a89bdbabe603a002f21731198" integrity sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw== @@ -7873,6 +4092,15 @@ resolve@^1.1.7, resolve@^1.10.1, resolve@^1.12.0, resolve@^1.20.0, resolve@^1.22 path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" +resolve@^1.22.1: + version "1.22.1" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177" + integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw== + dependencies: + is-core-module "^2.9.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + resolve@^2.0.0-next.3: version "2.0.0-next.3" resolved "https://registry.yarnpkg.com/resolve/-/resolve-2.0.0-next.3.tgz#d41016293d4a8586a39ca5d9b5f15cbea1f55e46" @@ -7881,14 +4109,6 @@ resolve@^2.0.0-next.3: is-core-module "^2.2.0" path-parse "^1.0.6" -ret@~0.1.10: - version "0.1.15" - resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" - -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" @@ -7904,25 +4124,19 @@ rgba-regex@^1.0.0: resolved "https://registry.yarnpkg.com/rgba-regex/-/rgba-regex-1.0.0.tgz#43374e2e2ca0968b0ef1523460b7d730ff22eeb3" integrity sha1-QzdOLiyglosO8VI0YLfXMP8i7rM= -rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.3: - version "2.7.1" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" - dependencies: - glob "^7.1.3" - -rimraf@^3.0.0, rimraf@^3.0.2: +rimraf@3.0.2, rimraf@^3.0.0, rimraf@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== dependencies: glob "^7.1.3" -ripemd160@^2.0.0, ripemd160@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" - dependencies: - hash-base "^3.0.0" - inherits "^2.0.1" +rollup@^2.79.1: + version "2.79.1" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.79.1.tgz#bedee8faef7c9f93a2647ac0108748f497f081c7" + integrity sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw== + optionalDependencies: + fsevents "~2.3.2" run-parallel@^1.1.9: version "1.2.0" @@ -7931,210 +4145,42 @@ run-parallel@^1.1.9: 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" - dependencies: - aproba "^1.1.1" - rxjs-compat@^6.5.4: version "6.6.3" resolved "https://registry.yarnpkg.com/rxjs-compat/-/rxjs-compat-6.6.3.tgz#141405fcee11f48718d428b99c8f01826f594e5c" integrity sha512-y+wUqq7bS2dG+7rH2fNMoxsDiJ32RQzFxZQE/JdtpnmEZmwLQrb1tCiItyHxdXJHXjmHnnzFscn3b6PEmORGKw== -safe-buffer@5.1.2, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: +safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" -safe-regex@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" - dependencies: - ret "~0.1.10" - -"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0": +"safer-buffer@>= 2.1.2 < 3.0.0": version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== -sax@^1.2.4: - version "1.2.4" - resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" - -scheduler@^0.17.0: - version "0.17.0" - resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.17.0.tgz#7c9c673e4ec781fac853927916d1c426b6f3ddfe" +scheduler@^0.23.0: + version "0.23.0" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.0.tgz#ba8041afc3d30eb206a487b6b384002e4e61fdfe" + integrity sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw== dependencies: loose-envify "^1.1.0" - object-assign "^4.1.1" - -scheduler@^0.19.1: - version "0.19.1" - resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.19.1.tgz#4f3e2ed2c1a7d65681f4c854fa8c5a1ccb40f196" - integrity sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA== - dependencies: - loose-envify "^1.1.0" - object-assign "^4.1.1" - -schema-utils@2.7.0, schema-utils@^2.6.5: - 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: - ajv "^6.1.0" - ajv-errors "^1.0.0" - ajv-keywords "^3.1.0" - -schema-utils@^3.0.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.1.1.tgz#bc74c4b6b6995c1d88f76a8b77bea7219e0c8281" - integrity sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw== - dependencies: - "@types/json-schema" "^7.0.8" - ajv "^6.12.5" - ajv-keywords "^3.5.2" - -select-hose@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" -selfsigned@^1.10.7: - version "1.10.7" - resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-1.10.7.tgz#da5819fd049d5574f28e88a9bcc6dbc6e6f3906b" - dependencies: - node-forge "0.9.0" - -semver@7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" - integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== +semver@^6.1.0, semver@^6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" -semver@7.x, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7: +semver@^7.3.7: version "7.3.7" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f" integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g== dependencies: lru-cache "^6.0.0" -semver@^5.3.0, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0: - version "5.7.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" - -semver@^6.0.0, semver@^6.1.0, semver@^6.3.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" - -send@0.17.1: - version "0.17.1" - resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8" - dependencies: - debug "2.6.9" - depd "~1.1.2" - destroy "~1.0.4" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - fresh "0.5.2" - http-errors "~1.7.2" - mime "1.6.0" - ms "2.1.1" - on-finished "~2.3.0" - range-parser "~1.2.1" - statuses "~1.5.0" - -serialize-javascript@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-3.1.0.tgz#8bf3a9170712664ef2561b44b691eafe399214ea" - integrity sha512-JIJT1DGiWmIKhzRsG91aS6Ze4sFUrYbltlkg2onR5OrnNM02Kl/hnY/T4FN2omvyeBbQmMJv+K4cPOpGzOTFBg== - dependencies: - randombytes "^2.1.0" - -serialize-javascript@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-5.0.1.tgz#7886ec848049a462467a97d3d918ebb2aaf934f4" - integrity sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA== - dependencies: - randombytes "^2.1.0" - -serve-index@^1.9.1: - version "1.9.1" - resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239" - dependencies: - accepts "~1.3.4" - batch "0.6.1" - debug "2.6.9" - escape-html "~1.0.3" - http-errors "~1.6.2" - mime-types "~2.1.17" - parseurl "~1.3.2" - -serve-static@1.14.1: - version "1.14.1" - resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9" - dependencies: - encodeurl "~1.0.2" - escape-html "~1.0.3" - parseurl "~1.3.3" - send "0.17.1" - -set-blocking@^2.0.0, set-blocking@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" - -set-value@^0.4.3: - version "0.4.3" - resolved "https://registry.yarnpkg.com/set-value/-/set-value-0.4.3.tgz#7db08f9d3d22dc7f78e53af3c3bf4666ecdfccf1" - dependencies: - extend-shallow "^2.0.1" - is-extendable "^0.1.1" - is-plain-object "^2.0.1" - to-object-path "^0.3.0" - -set-value@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.0.tgz#71ae4a88f0feefbbf52d1ea604f3fb315ebb6274" - dependencies: - extend-shallow "^2.0.1" - is-extendable "^0.1.1" - is-plain-object "^2.0.3" - split-string "^3.0.1" - -setimmediate@^1.0.4: - version "1.0.5" - resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" - -setprototypeof@1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" - -setprototypeof@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" - -sha.js@^2.4.0, sha.js@^2.4.8: - version "2.4.11" - resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" - dependencies: - inherits "^2.0.1" - safe-buffer "^5.0.1" - shallowequal@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8" -shebang-command@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" - dependencies: - shebang-regex "^1.0.0" - shebang-command@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" @@ -8142,10 +4188,6 @@ shebang-command@^2.0.0: dependencies: shebang-regex "^3.0.0" -shebang-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" - shebang-regex@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" @@ -8160,284 +4202,37 @@ side-channel@^1.0.4: get-intrinsic "^1.0.2" object-inspect "^1.9.0" -signal-exit@^3.0.0, signal-exit@^3.0.3, signal-exit@^3.0.7: - version "3.0.7" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" - integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== - simple-swizzle@^0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" dependencies: is-arrayish "^0.3.1" -sisteransi@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" - integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== - slash@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== -snapdragon-node@^2.0.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" - dependencies: - define-property "^1.0.0" - isobject "^3.0.0" - snapdragon-util "^3.0.1" - -snapdragon-util@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/snapdragon-util/-/snapdragon-util-3.0.1.tgz#f956479486f2acd79700693f6f7b805e45ab56e2" - dependencies: - kind-of "^3.2.0" - -snapdragon@^0.8.1: - version "0.8.2" - resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.2.tgz#64922e7c565b0e14204ba1aa7d6964278d25182d" - dependencies: - base "^0.11.1" - debug "^2.2.0" - define-property "^0.2.5" - extend-shallow "^2.0.1" - map-cache "^0.2.2" - source-map "^0.5.6" - source-map-resolve "^0.5.0" - use "^3.1.0" - -sockette@^2.0.6: +sockette@2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/sockette/-/sockette-2.0.6.tgz#63b533f3cfe3b592fc84178beea6577fa18cebf3" - -sockjs-client@1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/sockjs-client/-/sockjs-client-1.4.0.tgz#c9f2568e19c8fd8173b4997ea3420e0bb306c7d5" - dependencies: - debug "^3.2.5" - eventsource "^1.0.7" - faye-websocket "~0.11.1" - inherits "^2.0.3" - json3 "^3.3.2" - url-parse "^1.4.3" - -sockjs@0.3.20: - version "0.3.20" - resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.20.tgz#b26a283ec562ef8b2687b44033a4eeceac75d855" - integrity sha512-SpmVOVpdq0DJc0qArhF3E5xsxvaiqGNb73XfgBpK1y3UD5gs8DSo8aCTsuT5pX8rssdc2NDIzANwP9eCAiSdTA== - dependencies: - faye-websocket "^0.10.0" - uuid "^3.4.0" - websocket-driver "0.6.5" - -source-list-map@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.0.tgz#aaa47403f7b245a92fbc97ea08f250d6087ed085" + integrity sha512-W6iG8RGV6Zife3Cj+FhuyHV447E6fqFM2hKmnaQrTvg3OydINV3Msj3WPFbX76blUlUxvQSMMMdrJxce8NqI5Q== source-map-js@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== -source-map-loader@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/source-map-loader/-/source-map-loader-1.1.3.tgz#7dbc2fe7ea09d3e43c51fd9fc478b7f016c1f820" - integrity sha512-6YHeF+XzDOrT/ycFJNI53cgEsp/tHTMl37hi7uVyqFAlTXW109JazaQCkbc+jjoL2637qkH1amLi+JzrIpt5lA== - dependencies: - abab "^2.0.5" - iconv-lite "^0.6.2" - loader-utils "^2.0.0" - schema-utils "^3.0.0" - source-map "^0.6.1" - whatwg-mimetype "^2.3.0" - -source-map-resolve@^0.5.0: - version "0.5.2" - resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.2.tgz#72e2cc34095543e43b2c62b2c4c10d4a9054f259" - dependencies: - atob "^2.1.1" - decode-uri-component "^0.2.0" - resolve-url "^0.2.1" - source-map-url "^0.4.0" - urix "^0.1.0" - -source-map-resolve@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.6.0.tgz#3d9df87e236b53f16d01e58150fc7711138e5ed2" - integrity sha512-KXBr9d/fO/bWo97NXsPIAW1bFSBOuCnjbNTBMO7N59hsv5i9yzRDfcYwwt0l04+VqnKC+EwzvJZIP/qkuMgR/w== - dependencies: - atob "^2.1.2" - decode-uri-component "^0.2.0" - -source-map-support@0.5.13: - version "0.5.13" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932" - dependencies: - buffer-from "^1.0.0" - source-map "^0.6.0" - -source-map-support@~0.5.12, source-map-support@~0.5.20: - version "0.5.21" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" - integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== - dependencies: - buffer-from "^1.0.0" - source-map "^0.6.0" - -source-map-url@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" - -source-map@^0.5.6: - version "0.5.7" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" - -source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" - -source-map@^0.7.3: - version "0.7.3" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" - -spdy-transport@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/spdy-transport/-/spdy-transport-3.0.0.tgz#00d4863a6400ad75df93361a1608605e5dcdcf31" - dependencies: - debug "^4.1.0" - detect-node "^2.0.4" - hpack.js "^2.1.6" - obuf "^1.1.2" - readable-stream "^3.0.6" - wbuf "^1.7.3" - -spdy@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/spdy/-/spdy-4.0.2.tgz#b74f466203a3eda452c02492b91fb9e84a27677b" - integrity sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA== - dependencies: - debug "^4.1.0" - handle-thing "^2.0.0" - http-deceiver "^1.2.7" - select-hose "^2.0.0" - spdy-transport "^3.0.0" - -split-string@^3.0.1, split-string@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" - dependencies: - extend-shallow "^3.0.0" - -sprintf-js@~1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" - -ssri@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.1.tgz#2a3c41b28dd45b62b63676ecb74001265ae9edd8" - dependencies: - figgy-pudding "^3.5.1" - -ssri@^8.0.1: - version "8.0.1" - resolved "https://registry.yarnpkg.com/ssri/-/ssri-8.0.1.tgz#638e4e439e2ffbd2cd289776d5ca457c4f51a2af" - integrity sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ== - dependencies: - minipass "^3.1.1" - -stack-utils@^2.0.3: - version "2.0.5" - resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.5.tgz#d25265fca995154659dbbfba3b49254778d2fdd5" - integrity sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA== - dependencies: - escape-string-regexp "^2.0.0" - -static-extend@^0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" - dependencies: - define-property "^0.2.5" - object-copy "^0.1.0" - -"statuses@>= 1.4.0 < 2", "statuses@>= 1.5.0 < 2", statuses@~1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" - -stream-browserify@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.1.tgz#66266ee5f9bdb9940a4e4514cafb43bb71e5c9db" - dependencies: - inherits "~2.0.1" - readable-stream "^2.0.2" - -stream-each@^1.1.0: - version "1.2.2" - resolved "https://registry.yarnpkg.com/stream-each/-/stream-each-1.2.2.tgz#8e8c463f91da8991778765873fe4d960d8f616bd" - dependencies: - end-of-stream "^1.1.0" - stream-shift "^1.0.0" - -stream-http@^2.7.2: - version "2.8.3" - resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.8.3.tgz#b2d242469288a5a27ec4fe8933acf623de6514fc" - dependencies: - builtin-status-codes "^3.0.0" - inherits "^2.0.1" - readable-stream "^2.3.6" - to-arraybuffer "^1.0.0" - xtend "^4.0.0" - -stream-shift@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.0.tgz#d5c752825e5367e786f78e18e445ea223a155952" - -string-length@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" - integrity sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ== - dependencies: - char-regex "^1.0.2" - strip-ansi "^6.0.0" +sourcemap-codec@^1.4.8: + version "1.4.8" + resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" + integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA== string-similarity@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/string-similarity/-/string-similarity-4.0.3.tgz#ef52d6fc59c8a0fc93b6307fbbc08cc6e18cde21" integrity sha512-QEwJzNFCqq+5AGImk5z4vbsEPTN/+gtyKfXBVLBcbPBRPNganZGfQnIuf9yJ+GiwSnD65sT8xrw/uwU1Q1WmfQ== -string-width@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" - dependencies: - code-point-at "^1.0.0" - is-fullwidth-code-point "^1.0.0" - strip-ansi "^3.0.0" - -"string-width@^1.0.2 || 2": - version "2.1.1" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" - dependencies: - is-fullwidth-code-point "^2.0.0" - strip-ansi "^4.0.0" - -string-width@^3.0.0, string-width@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" - dependencies: - emoji-regex "^7.0.1" - is-fullwidth-code-point "^2.0.0" - strip-ansi "^5.1.0" - -string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - string.prototype.matchall@^4.0.7: version "4.0.7" resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.7.tgz#8e6ecb0d8a1fb1fda470d81acecb2dba057a481d" @@ -8470,104 +4265,61 @@ string.prototype.trimstart@^1.0.5: define-properties "^1.1.4" es-abstract "^1.19.5" -string_decoder@^1.0.0, string_decoder@^1.1.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.2.0.tgz#fe86e738b19544afe70469243b2a1ee9240eae8d" - dependencies: - safe-buffer "~5.1.0" - string_decoder@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== dependencies: safe-buffer "~5.1.0" -strip-ansi@^3.0.0, strip-ansi@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" - dependencies: - ansi-regex "^2.0.0" - -strip-ansi@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" - dependencies: - ansi-regex "^3.0.0" - -strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" - dependencies: - ansi-regex "^4.1.0" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: +strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-bom@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" - integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== - -strip-eof@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" - -strip-final-newline@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" - integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== - -strip-indent@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001" - integrity sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ== + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== dependencies: - min-indent "^1.0.0" + ansi-regex "^5.0.1" strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== -strip-json-comments@~2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" - -style-loader@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-2.0.0.tgz#9669602fd4690740eaaec137799a03addbbc393c" - integrity sha512-Z0gYUJmzZ6ZdRUqpg1r8GsaFKypE+3xAzuFeMuoHgjc9KZv3wMyCRjQIWEbhoFSq7+7yoHXySDJyyWQaPajeiQ== +strip-literal@^0.4.2: + version "0.4.2" + resolved "https://registry.yarnpkg.com/strip-literal/-/strip-literal-0.4.2.tgz#4f9fa6c38bb157b924e9ace7155ebf8a2342cbcf" + integrity sha512-pv48ybn4iE1O9RLgCAN0iU4Xv7RlBTiit6DKmMiErbs9x1wH6vXBs45tWc0H5wUIF6TLTrKweqkmYF/iraQKNw== dependencies: - loader-utils "^2.0.0" - schema-utils "^3.0.0" + acorn "^8.8.0" -style-value-types@5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/style-value-types/-/style-value-types-5.0.0.tgz#76c35f0e579843d523187989da866729411fc8ad" - integrity sha512-08yq36Ikn4kx4YU6RD7jWEv27v4V+PUsOGa4n/as8Et3CuODMJQ00ENeAVXAeydX4Z2j1XHZF1K2sX4mGl18fA== +style-mod@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/style-mod/-/style-mod-4.0.0.tgz#97e7c2d68b592975f2ca7a63d0dd6fcacfe35a01" + integrity sha512-OPhtyEjyyN9x3nhPsu76f52yUGXiZcgvsrFVtvTkyGRQJ0XK+GPc6ov1z+lRpbeabka+MYEQxOYRnt5nF30aMw== + +style-value-types@5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/style-value-types/-/style-value-types-5.1.2.tgz#6be66b237bd546048a764883528072ed95713b62" + integrity sha512-Vs9fNreYF9j6W2VvuDTP7kepALi7sk0xtk2Tu8Yxi9UoajJdEVpNpCov0HsLTqXvNGKX+Uv09pkozVITi1jf3Q== dependencies: hey-listen "^1.0.8" - tslib "^2.1.0" + tslib "2.4.0" -styled-components-breakpoint@^3.0.0-preview.20: +styled-components-breakpoint@3.0.0-preview.20: version "3.0.0-preview.20" resolved "https://registry.yarnpkg.com/styled-components-breakpoint/-/styled-components-breakpoint-3.0.0-preview.20.tgz#877e88a00c0cf66976f610a1d347839a1a0b6d70" + integrity sha512-rZ+Upo9lJfzK4xXRZxlvAsT90jaONa5VtoNT18fXaMLd+J75vCD1MU4/pwT/Y5Jw4rzGztMtZpelVC6P+AuNeA== -styled-components@^5.2.1: - version "5.2.1" - resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-5.2.1.tgz#6ed7fad2dc233825f64c719ffbdedd84ad79101a" - integrity sha512-sBdgLWrCFTKtmZm/9x7jkIabjFNVzCUeKfoQsM6R3saImkUnjx0QYdLwJHBjY9ifEcmjDamJDVfknWm1yxZPxQ== +styled-components@5.3.6: + version "5.3.6" + resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-5.3.6.tgz#27753c8c27c650bee9358e343fc927966bfd00d1" + integrity sha512-hGTZquGAaTqhGWldX7hhfzjnIYBZ0IXQXkCYdvF1Sq3DsUaLx6+NTHC5Jj1ooM2F68sBiVz3lvhfwQs/S3l6qg== dependencies: "@babel/helper-module-imports" "^7.0.0" "@babel/traverse" "^7.4.5" - "@emotion/is-prop-valid" "^0.8.8" + "@emotion/is-prop-valid" "^1.1.0" "@emotion/stylis" "^0.8.4" "@emotion/unitless" "^0.7.4" - babel-plugin-styled-components ">= 1" + babel-plugin-styled-components ">= 1.12.0" css-to-react-native "^3.0.0" hoist-non-react-statics "^3.0.0" shallowequal "^1.1.0" @@ -8579,67 +4331,67 @@ supports-color@^5.3.0, supports-color@^5.5.0: dependencies: has-flag "^3.0.0" -supports-color@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3" - dependencies: - has-flag "^3.0.0" - -supports-color@^7.0.0, supports-color@^7.1.0: +supports-color@^7.1.0: version "7.2.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== dependencies: has-flag "^4.0.0" -supports-color@^8.0.0: - version "8.1.1" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" - integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== - dependencies: - has-flag "^4.0.0" - -supports-hyperlinks@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz#4f77b42488765891774b70c79babd87f9bd594bb" - integrity sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ== - dependencies: - has-flag "^4.0.0" - supports-color "^7.0.0" - supports-preserve-symlinks-flag@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== -svg-url-loader@^7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/svg-url-loader/-/svg-url-loader-7.1.1.tgz#0cbdb30beb8679cb060c12eaf30085747fa7591f" - integrity sha512-NlsMCePODm7FQhU9aEZyGLPx5Xe1QRI1cSEUE6vTq5LJc9l9pStagvXoEIyZ9O3r00w6G3+Wbkimb+SC3DI/Aw== - dependencies: - file-loader "~6.2.0" - loader-utils "~2.0.0" +swr@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/swr/-/swr-1.3.0.tgz#c6531866a35b4db37b38b72c45a63171faf9f4e8" + integrity sha512-dkghQrOl2ORX9HYrMDtPa7LTVHJjCTeZoB1dqTbnnEDlSvN8JEKpYIYurDfvbQFUUS8Cg8PceFVZNkW0KNNYPw== -swr@^0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/swr/-/swr-0.2.3.tgz#e0fb260d27f12fafa2388312083368f45127480d" - integrity sha512-JhuuD5ojqgjAQpZAhoPBd8Di0Mr1+ykByVKuRJdtKaxkUX/y8kMACWKkLgLQc8pcDOKEAnbIreNjU7HfqI9nHQ== +sync-request@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/sync-request/-/sync-request-6.1.0.tgz#e96217565b5e50bbffe179868ba75532fb597e68" + integrity sha512-8fjNkrNlNCrVc/av+Jn+xxqfCjYaBoHqCsDz6mt030UMxJGr+GSfCV1dQt2gRtlL63+VPidwDVLr7V2OcTSdRw== dependencies: - fast-deep-equal "2.0.1" - -symbol-observable@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" + http-response-object "^3.0.1" + sync-rpc "^1.2.1" + then-request "^6.0.0" -symbol-observable@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-2.0.3.tgz#5b521d3d07a43c351055fa43b8355b62d33fd16a" - integrity sha512-sQV7phh2WCYAn81oAkakC5qjq2Ml0g8ozqz03wOGnx9dDlG1de6yrF+0RAzSJD8fPUow3PTSMf2SAbOGxb93BA== +sync-rpc@^1.2.1: + version "1.3.6" + resolved "https://registry.yarnpkg.com/sync-rpc/-/sync-rpc-1.3.6.tgz#b2e8b2550a12ccbc71df8644810529deb68665a7" + integrity sha512-J8jTXuZzRlvU7HemDgHi3pGnh/rkoqR/OZSjhTyyZrEkkYQbk7Z33AXp37mkPfPpfdOuj7Ex3H/TJM1z48uPQw== + dependencies: + get-port "^3.1.0" -synchronous-promise@^2.0.10: - version "2.0.13" - resolved "https://registry.yarnpkg.com/synchronous-promise/-/synchronous-promise-2.0.13.tgz#9d8c165ddee69c5a6542862b405bc50095926702" - integrity sha512-R9N6uDkVsghHePKh1TEqbnLddO2IY25OcsksyFp/qBe7XYd0PVbKEWxhcdMhpLzE1I6skj5l4aEZ3CRxcbArlA== +tailwindcss@3.2.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.2.2.tgz#705f78cec8f4de2feb52abdb7a8a056e67f2d736" + integrity sha512-c2GtSdqg+harR4QeoTmex0Ngfg8IIHNeLQH5yr2B9uZbZR1Xt1rYbjWOWTcj3YLTZhrmZnPowoQDbSRFyZHQ5Q== + dependencies: + arg "^5.0.2" + chokidar "^3.5.3" + color-name "^1.1.4" + detective "^5.2.1" + didyoumean "^1.2.2" + dlv "^1.1.3" + fast-glob "^3.2.12" + glob-parent "^6.0.2" + is-glob "^4.0.3" + lilconfig "^2.0.6" + micromatch "^4.0.5" + normalize-path "^3.0.0" + object-hash "^3.0.0" + picocolors "^1.0.0" + postcss "^8.4.18" + postcss-import "^14.1.0" + postcss-js "^4.0.0" + postcss-load-config "^3.1.4" + postcss-nested "6.0.0" + postcss-selector-parser "^6.0.10" + postcss-value-parser "^4.2.0" + quick-lru "^5.1.1" + resolve "^1.22.1" tailwindcss@^2.2.7: version "2.2.19" @@ -8679,164 +4431,50 @@ tailwindcss@^2.2.7: resolve "^1.20.0" tmp "^0.2.1" -tailwindcss@^3.0.24: - version "3.0.24" - resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.0.24.tgz#22e31e801a44a78a1d9a81ecc52e13b69d85704d" - integrity sha512-H3uMmZNWzG6aqmg9q07ZIRNIawoiEcNFKDfL+YzOPuPsXuDXxJxB9icqzLgdzKNwjG3SAro2h9SYav8ewXNgig== - dependencies: - arg "^5.0.1" - chokidar "^3.5.3" - color-name "^1.1.4" - detective "^5.2.0" - didyoumean "^1.2.2" - dlv "^1.1.3" - fast-glob "^3.2.11" - glob-parent "^6.0.2" - is-glob "^4.0.3" - lilconfig "^2.0.5" - normalize-path "^3.0.0" - object-hash "^3.0.0" - picocolors "^1.0.0" - postcss "^8.4.12" - postcss-js "^4.0.0" - postcss-load-config "^3.1.4" - postcss-nested "5.0.6" - postcss-selector-parser "^6.0.10" - postcss-value-parser "^4.2.0" - quick-lru "^5.1.1" - resolve "^1.22.0" - -tapable@^1.0.0, tapable@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" - -tar@^4: - version "4.4.4" - resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.4.tgz#ec8409fae9f665a4355cc3b4087d0820232bb8cd" - dependencies: - chownr "^1.0.1" - fs-minipass "^1.2.5" - minipass "^2.3.3" - minizlib "^1.1.0" - mkdirp "^0.5.0" - safe-buffer "^5.1.2" - yallist "^3.0.2" - -tar@^6.0.2: - version "6.1.11" - resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.11.tgz#6760a38f003afa1b2ffd0ffe9e9abbd0eab3d621" - integrity sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA== - dependencies: - chownr "^2.0.0" - fs-minipass "^2.0.0" - minipass "^3.0.0" - minizlib "^2.1.1" - mkdirp "^1.0.3" - yallist "^4.0.0" - -terminal-link@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/terminal-link/-/terminal-link-2.1.1.tgz#14a64a27ab3c0df933ea546fba55f2d078edc994" - integrity sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ== - dependencies: - ansi-escapes "^4.2.1" - supports-hyperlinks "^2.0.0" - -terser-webpack-plugin@^1.4.3: - version "1.4.4" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.4.4.tgz#2c63544347324baafa9a56baaddf1634c8abfc2f" - integrity sha512-U4mACBHIegmfoEe5fdongHESNJWqsGU+W0S/9+BmYGVQDw1+c2Ow05TpMhxjPK1sRb7cuYq1BPl1e5YHJMTCqA== - dependencies: - cacache "^12.0.2" - find-cache-dir "^2.1.0" - is-wsl "^1.1.0" - schema-utils "^1.0.0" - serialize-javascript "^3.1.0" - source-map "^0.6.1" - terser "^4.1.2" - webpack-sources "^1.4.0" - worker-farm "^1.7.0" - -terser-webpack-plugin@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-4.2.3.tgz#28daef4a83bd17c1db0297070adc07fc8cfc6a9a" - integrity sha512-jTgXh40RnvOrLQNgIkwEKnQ8rmHjHK4u+6UBEi+W+FPmvb+uo+chJXntKe7/3lW5mNysgSWD60KyesnhW8D6MQ== - dependencies: - cacache "^15.0.5" - find-cache-dir "^3.3.1" - jest-worker "^26.5.0" - p-limit "^3.0.2" - schema-utils "^3.0.0" - serialize-javascript "^5.0.1" - source-map "^0.6.1" - terser "^5.3.4" - webpack-sources "^1.4.3" - -terser@^4.1.2: - version "4.3.1" - resolved "https://registry.yarnpkg.com/terser/-/terser-4.3.1.tgz#09820bcb3398299c4b48d9a86aefc65127d0ed65" - dependencies: - commander "^2.20.0" - source-map "~0.6.1" - source-map-support "~0.5.12" - -terser@^5.3.4: - version "5.14.0" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.14.0.tgz#eefeec9af5153f55798180ee2617f390bdd285e2" - integrity sha512-JC6qfIEkPBd9j1SMO3Pfn+A6w2kQV54tv+ABQLgZr7dA3k/DL/OBoYSWxzVpZev3J+bUHXfr55L8Mox7AaNo6g== - dependencies: - "@jridgewell/source-map" "^0.3.2" - acorn "^8.5.0" - commander "^2.20.0" - source-map-support "~0.5.20" - -test-exclude@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" - integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== - dependencies: - "@istanbuljs/schema" "^0.1.2" - glob "^7.1.4" - minimatch "^3.0.4" - text-table@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" -throat@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/throat/-/throat-6.0.1.tgz#d514fedad95740c12c2d7fc70ea863eb51ade375" - integrity sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w== - -through2@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.3.tgz#0004569b37c7c74ba39c43f3ced78d1ad94140be" - dependencies: - readable-stream "^2.1.5" - xtend "~4.0.1" - -thunky@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.0.3.tgz#f5df732453407b09191dae73e2a8cc73f381a826" - -timers-browserify@^2.0.4: - version "2.0.10" - resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.10.tgz#1d28e3d2aadf1d5a5996c4e9f95601cd053480ae" - dependencies: - setimmediate "^1.0.4" +then-request@^6.0.0: + version "6.0.2" + resolved "https://registry.yarnpkg.com/then-request/-/then-request-6.0.2.tgz#ec18dd8b5ca43aaee5cb92f7e4c1630e950d4f0c" + integrity sha512-3ZBiG7JvP3wbDzA9iNY5zJQcHL4jn/0BWtXIkagfz7QgOL/LqjCEOBQuJNZfu0XYnv5JhKh+cDxCPM4ILrqruA== + dependencies: + "@types/concat-stream" "^1.6.0" + "@types/form-data" "0.0.33" + "@types/node" "^8.0.0" + "@types/qs" "^6.2.31" + caseless "~0.12.0" + concat-stream "^1.6.0" + form-data "^2.2.0" + http-basic "^8.1.1" + http-response-object "^3.0.1" + promise "^8.0.0" + qs "^6.4.0" timsort@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4" -tiny-invariant@^1.0.2: - version "1.0.4" - resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.0.4.tgz#346b5415fd93cb696b0c4e8a96697ff590f92463" - -tiny-warning@^1.0.0, tiny-warning@^1.0.2: +tiny-warning@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.2.tgz#1dfae771ee1a04396bdfde27a3adcebc6b648b28" +tinybench@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/tinybench/-/tinybench-2.3.1.tgz#14f64e6b77d7ef0b1f6ab850c7a808c6760b414d" + integrity sha512-hGYWYBMPr7p4g5IarQE7XhlyWveh1EKhy4wUBS1LrHXCKYgvz+4/jCqgmJqZxxldesn05vccrtME2RLLZNW7iA== + +tinypool@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/tinypool/-/tinypool-0.3.0.tgz#c405d8b743509fc28ea4ca358433190be654f819" + integrity sha512-NX5KeqHOBZU6Bc0xj9Vr5Szbb1j8tUHIeD18s41aDJaPeC5QTdEhK0SpdpUrZlj2nv5cctNcSjaKNanXlfcVEQ== + +tinyspy@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/tinyspy/-/tinyspy-1.0.2.tgz#6da0b3918bfd56170fb3cd3a2b5ef832ee1dff0d" + integrity sha512-bSGlgwLBYf7PnUsQ6WOc6SJ3pGOcd+d8AA6EUnLDDM0kWEstC1JIlSZA3UNliDXhd9ABoS7hiRBDCu+XP/sf1Q== + tmp@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14" @@ -8844,32 +4482,10 @@ tmp@^0.2.1: dependencies: rimraf "^3.0.0" -tmpl@1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" - integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== - -to-arraybuffer@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43" - to-fast-properties@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" -to-object-path@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" - dependencies: - kind-of "^3.0.2" - -to-regex-range@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" - dependencies: - is-number "^3.0.0" - repeat-string "^1.6.1" - to-regex-range@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" @@ -8877,24 +4493,11 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" -to-regex@^3.0.1, to-regex@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" - dependencies: - define-property "^2.0.2" - extend-shallow "^3.0.2" - regex-not "^1.0.2" - safe-regex "^1.1.0" - toggle-selection@^1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/toggle-selection/-/toggle-selection-1.0.6.tgz#6e45b1263f2017fa0acc7d89d78b15b8bf77da32" integrity sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ== -toidentifier@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" - toposort@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/toposort/-/toposort-2.0.2.tgz#ae21768175d1559d48bef35420b2f4962f09c330" @@ -8904,45 +4507,26 @@ tr46@~0.0.3: resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== -tryer@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/tryer/-/tryer-1.0.1.tgz#f2c85406800b9b0f74c9f7465b81eaad241252f8" - integrity sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA== - -ts-essentials@^9.1.2: - version "9.1.2" - resolved "https://registry.yarnpkg.com/ts-essentials/-/ts-essentials-9.1.2.tgz#46db6944b73b4cd603f3d959ef1123c16ba56f59" - integrity sha512-EaSmXsAhEiirrTY1Oaa7TSpei9dzuCuFPmjKRJRPamERYtfaGS8/KpOSbjergLz/Y76/aZlV9i/krgzsuWEBbg== - -ts-jest@^28.0.5: - version "28.0.5" - resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-28.0.5.tgz#31776f768fba6dfc8c061d488840ed0c8eeac8b9" - integrity sha512-Sx9FyP9pCY7pUzQpy4FgRZf2bhHY3za576HMKJFs+OnQ9jS96Du5vNsDKkyedQkik+sEabbKAnCliv9BEsHZgQ== - dependencies: - bs-logger "0.x" - fast-json-stable-stringify "2.x" - jest-util "^28.0.0" - json5 "^2.2.1" - lodash.memoize "4.x" - make-error "1.x" - semver "7.x" - yargs-parser "^21.0.1" - -ts-toolbelt@^8.0.7: - version "8.0.7" - resolved "https://registry.yarnpkg.com/ts-toolbelt/-/ts-toolbelt-8.0.7.tgz#4dad2928831a811ee17dbdab6eb1919fc0a295bf" - integrity sha512-KICHyKxc5Nu34kyoODrEe2+zvuQQaubTJz7pnC5RQ19TH/Jged1xv+h8LBrouaSD310m75oAljYs59LNHkLDkQ== +ts-essentials@9.3.0: + version "9.3.0" + resolved "https://registry.yarnpkg.com/ts-essentials/-/ts-essentials-9.3.0.tgz#7e639c1a76b1805c3c60d6e1b5178da2e70aea02" + integrity sha512-XeiCboEyBG8UqXZtXl59bWEi4ZgOqRsogFDI6WDGIF1LmzbYiAkIwjkXN6zZWWl4re/lsOqMlYfe8KA0XiiEPw== -tslib@^1.0.0, tslib@^1.10.0, tslib@^1.8.1, tslib@^1.9.0: - version "1.14.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" - integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== +ts-toolbelt@^9.6.0: + version "9.6.0" + resolved "https://registry.yarnpkg.com/ts-toolbelt/-/ts-toolbelt-9.6.0.tgz#50a25426cfed500d4a09bd1b3afb6f28879edfd5" + integrity sha512-nsZd8ZeNUzukXPlJmTBwUAuABDe/9qtVDelJeT/qW0ow3ZS3BsQJtNkan1802aM9Uf68/Y8ljw86Hu0h5IUW3w== -tslib@^2.1.0: +tslib@2.4.0, tslib@^2.3.1: version "2.4.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3" integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== +tslib@^1.0.0, tslib@^1.10.0, tslib@^1.8.1: + version "1.14.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" + integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== + tsutils@^3.21.0: version "3.21.0" resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" @@ -8950,11 +4534,7 @@ tsutils@^3.21.0: dependencies: tslib "^1.8.1" -tty-browserify@0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6" - -twin.macro@^2.8.2: +twin.macro@2.8.2: version "2.8.2" resolved "https://registry.yarnpkg.com/twin.macro/-/twin.macro-2.8.2.tgz#7f1344b4b1c3811da93a62fa204fe08999df7a75" integrity sha512-2Vg09mp+nA70AWUedJ8WRgB2me3buq7JGbOnjHnFnNaBzomVu5k7lJ9YGpByIlre+UYr7QRhtlj7+IUKxvCrUA== @@ -8982,7 +4562,7 @@ type-check@^0.4.0, type-check@~0.4.0: dependencies: prelude-ls "^1.2.1" -type-detect@4.0.8: +type-detect@^4.0.0, type-detect@^4.0.5: version "4.0.8" resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== @@ -8992,26 +4572,15 @@ type-fest@^0.20.2: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== -type-fest@^0.21.3: - version "0.21.3" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" - integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== - -type-is@~1.6.17, type-is@~1.6.18: - version "1.6.18" - resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" - dependencies: - media-typer "0.3.0" - mime-types "~2.1.24" - typedarray@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" + integrity sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA== -typescript@^4.7.3: - version "4.7.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.3.tgz#8364b502d5257b540f9de4c40be84c98e23a129d" - integrity sha512-WOkT3XYvrpXx4vMMqlD+8R8R37fZkjyLGlxavMc4iB8lrl8L0DeTcHbYgw/v0N/z9wAFsgBhcsF0ruoySS22mA== +typescript@4.8.4: + version "4.8.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.4.tgz#c464abca159669597be5f96b8943500b238e60e6" + integrity sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ== unbox-primitive@^1.0.2: version "1.0.2" @@ -9023,67 +4592,11 @@ unbox-primitive@^1.0.2: has-symbols "^1.0.3" which-boxed-primitive "^1.0.2" -unicode-canonical-property-names-ecmascript@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818" - -unicode-match-property-ecmascript@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz#8ed2a32569961bce9227d09cd3ffbb8fed5f020c" - dependencies: - unicode-canonical-property-names-ecmascript "^1.0.4" - unicode-property-aliases-ecmascript "^1.0.4" - -unicode-match-property-value-ecmascript@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.2.0.tgz#0d91f600eeeb3096aa962b1d6fc88876e64ea531" - integrity sha512-wjuQHGQVofmSJv1uVISKLE5zO2rNGzM/KCYZch/QQvez7C1hUhBIuZ701fYXExuufJFMPhv2SyL8CyoIfMLbIQ== - -unicode-property-aliases-ecmascript@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.0.4.tgz#5a533f31b4317ea76f17d807fa0d116546111dd0" - -union-value@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.0.tgz#5c71c34cb5bad5dcebe3ea0cd08207ba5aa1aea4" - dependencies: - arr-union "^3.1.0" - get-value "^2.0.6" - is-extendable "^0.1.1" - set-value "^0.4.3" - -unique-filename@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230" - dependencies: - unique-slug "^2.0.0" - -unique-slug@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-2.0.0.tgz#db6676e7c7cc0629878ff196097c78855ae9f4ab" - dependencies: - imurmurhash "^0.1.4" - universalify@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== -unpipe@1.0.0, unpipe@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" - -unset-value@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559" - dependencies: - has-value "^0.3.1" - isobject "^3.0.0" - -upath@^1.1.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894" - update-browserslist-db@^1.0.0: version "1.0.4" resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.4.tgz#dbfc5a789caa26b1db8990796c2c8ebbce304824" @@ -9092,6 +4605,14 @@ update-browserslist-db@^1.0.0: escalade "^3.1.1" picocolors "^1.0.0" +update-browserslist-db@^1.0.9: + version "1.0.10" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz#0f54b876545726f17d00cd9a2561e6dade943ff3" + integrity sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ== + dependencies: + escalade "^3.1.1" + picocolors "^1.0.0" + uri-js@^4.2.2: version "4.4.1" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" @@ -9099,312 +4620,88 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" -urix@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" - -url-parse@^1.4.3: - version "1.4.4" - resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.4.tgz#cac1556e95faa0303691fec5cf9d5a1bc34648f8" - dependencies: - querystringify "^2.0.0" - requires-port "^1.0.0" - -url@^0.11.0: - version "0.11.0" - resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" - dependencies: - punycode "1.3.2" - querystring "0.2.0" - -use-fit-text@^2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/use-fit-text/-/use-fit-text-2.4.0.tgz#d3d1cd72f6d29cfb2233ec0e0b38c6f25d319f67" - integrity sha512-Iy4LMrXcdxWlyZ5phntMpJMgyXGB1p3tV73y2r0QrZ6f/thPh+/QU3ie6RCXmjF8tHMs20FKMPskXeDYIla/Ww== - dependencies: - resize-observer-polyfill "^1.5.1" - -use-isomorphic-layout-effect@^1.1.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz#497cefb13d863d687b08477d9e5a164ad8c1a6fb" - integrity sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA== - -use-memo-one@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/use-memo-one/-/use-memo-one-1.1.1.tgz#39e6f08fe27e422a7d7b234b5f9056af313bd22c" - integrity sha512-oFfsyun+bP7RX8X2AskHNTxu+R3QdE/RC5IefMbqptmACAA/gfol1KDD5KRzPsGMa62sWxGZw+Ui43u6x4ddoQ== - use-sync-external-store@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a" integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA== -use@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/use/-/use-3.1.0.tgz#14716bf03fdfefd03040aef58d8b4b85f3a7c544" - dependencies: - kind-of "^6.0.2" - -util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: +util-deprecate@^1.0.2, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" -util@0.10.3: - version "0.10.3" - resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9" - dependencies: - inherits "2.0.1" - -util@^0.11.0: - version "0.11.1" - resolved "https://registry.yarnpkg.com/util/-/util-0.11.1.tgz#3236733720ec64bb27f6e26f421aaa2e1b588d61" - dependencies: - inherits "2.0.3" - -utils-merge@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" - -uuid@^3.3.2, uuid@^3.4.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" - integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== - -uuid@^8.3.2: - version "8.3.2" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" - integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== - -v8-compile-cache@^2.0.3, v8-compile-cache@^2.1.1: +v8-compile-cache@^2.0.3: version "2.1.1" resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz#54bc3cdd43317bca91e35dcaf305b1a7237de745" integrity sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ== -v8-to-istanbul@^9.0.0: - version "9.0.1" - resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.0.1.tgz#b6f994b0b5d4ef255e17a0d17dc444a9f5132fa4" - integrity sha512-74Y4LqY74kLE6IFyIjPtkSTWzUZmj8tdHT9Ii/26dvQ6K9Dl2NbEfj0XgU2sHCtKgt5VupqhlO/5aWuqS+IY1w== - dependencies: - "@jridgewell/trace-mapping" "^0.3.12" - "@types/istanbul-lib-coverage" "^2.0.1" - convert-source-map "^1.6.0" - -value-equal@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-0.4.0.tgz#c5bdd2f54ee093c04839d71ce2e4758a6890abc7" - -vary@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" - -vm-browserify@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.0.tgz#bd76d6a23323e2ca8ffa12028dc04559c75f9019" - -void-elements@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-2.0.1.tgz#c066afb582bb1cb4128d60ea92392e94d5e9dbec" - -walker@^1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" - integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== - dependencies: - makeerror "1.0.12" - -watchpack-chokidar2@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/watchpack-chokidar2/-/watchpack-chokidar2-2.0.0.tgz#9948a1866cbbd6cb824dea13a7ed691f6c8ddff0" - integrity sha512-9TyfOyN/zLUbA288wZ8IsMZ+6cbzvsNyEzSBp6e/zkifi6xxbl8SmQ/CxQq32k8NNqrdVEVUVSEf56L4rQ/ZxA== +vite-plugin-full-reload@^1.0.1: + version "1.0.4" + resolved "https://registry.yarnpkg.com/vite-plugin-full-reload/-/vite-plugin-full-reload-1.0.4.tgz#3fecd446f9accd5af01eb0328f6d161dca7cfc45" + integrity sha512-9WejQII6zJ++m/YE173Zvl2jq4cqa404KNrVT+JDzDnqaGRq5UvOvA48fnsSWPIMXFV7S0dq5+sZqcSB+tKBgA== dependencies: - chokidar "^2.1.8" + picocolors "^1.0.0" + picomatch "^2.3.1" -watchpack@^1.6.1: - version "1.7.2" - resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.7.2.tgz#c02e4d4d49913c3e7e122c3325365af9d331e9aa" - integrity sha512-ymVbbQP40MFTp+cNMvpyBpBtygHnPzPkHqoIwRRj/0B8KhqQwV8LaKjtbaxF2lK4vl8zN9wCxS46IFCU5K4W0g== +vite@3.2.2, vite@^3.0.0: + version "3.2.2" + resolved "https://registry.yarnpkg.com/vite/-/vite-3.2.2.tgz#280762bfaf47bcea1d12698427331c0009ac7c1f" + integrity sha512-pLrhatFFOWO9kS19bQ658CnRYzv0WLbsPih6R+iFeEEhDOuYgYCX2rztUViMz/uy/V8cLCJvLFeiOK7RJEzHcw== dependencies: - graceful-fs "^4.1.2" - neo-async "^2.5.0" + esbuild "^0.15.9" + postcss "^8.4.18" + resolve "^1.22.1" + rollup "^2.79.1" optionalDependencies: - chokidar "^3.4.0" - watchpack-chokidar2 "^2.0.0" + fsevents "~2.3.2" -wbuf@^1.1.0, wbuf@^1.7.3: - version "1.7.3" - resolved "https://registry.yarnpkg.com/wbuf/-/wbuf-1.7.3.tgz#c1d8d149316d3ea852848895cb6a0bfe887b87df" +vitest@0.24.5: + version "0.24.5" + resolved "https://registry.yarnpkg.com/vitest/-/vitest-0.24.5.tgz#ba23acdf4362e3299ca2a8a55afe3d2e7402b763" + integrity sha512-zw6JhPUHtLILQDe5Q39b/SzoITkG+R7hcFjuthp4xsi6zpmfQPOZcHodZ+3bqoWl4EdGK/p1fuMiEwdxgbGLOA== dependencies: - minimalistic-assert "^1.0.0" + "@types/chai" "^4.3.3" + "@types/chai-subset" "^1.3.3" + "@types/node" "*" + chai "^4.3.6" + debug "^4.3.4" + local-pkg "^0.4.2" + strip-literal "^0.4.2" + tinybench "^2.3.1" + tinypool "^0.3.0" + tinyspy "^1.0.2" + vite "^3.0.0" + +void-elements@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09" + integrity sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w== + +w3c-keyname@^2.2.4: + version "2.2.6" + resolved "https://registry.yarnpkg.com/w3c-keyname/-/w3c-keyname-2.2.6.tgz#8412046116bc16c5d73d4e612053ea10a189c85f" + integrity sha512-f+fciywl1SJEniZHD6H+kUO8gOnwIr7f4ijKA6+ZvJFjeGi1r4PDLl53Ayud9O/rk64RqgoQine0feoeOU0kXg== webidl-conversions@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" integrity sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE= -webpack-assets-manifest@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/webpack-assets-manifest/-/webpack-assets-manifest-3.1.1.tgz#39bbc3bf2ee57fcd8ba07cda51c9ba4a3c6ae1de" - integrity sha512-JV9V2QKc5wEWQptdIjvXDUL1ucbPLH2f27toAY3SNdGZp+xSaStAgpoMcvMZmqtFrBc9a5pTS1058vxyMPOzRQ== - dependencies: - chalk "^2.0" - lodash.get "^4.0" - lodash.has "^4.0" - mkdirp "^0.5" - schema-utils "^1.0.0" - tapable "^1.0.0" - webpack-sources "^1.0.0" - -webpack-bundle-analyzer@^3.8.0: - version "3.8.0" - resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-3.8.0.tgz#ce6b3f908daf069fd1f7266f692cbb3bded9ba16" - integrity sha512-PODQhAYVEourCcOuU+NiYI7WdR8QyELZGgPvB1y2tjbUpbmcQOt5Q7jEK+ttd5se0KSBKD9SXHCEozS++Wllmw== - dependencies: - acorn "^7.1.1" - acorn-walk "^7.1.1" - bfj "^6.1.1" - chalk "^2.4.1" - commander "^2.18.0" - ejs "^2.6.1" - express "^4.16.3" - filesize "^3.6.1" - gzip-size "^5.0.0" - lodash "^4.17.15" - mkdirp "^0.5.1" - opener "^1.5.1" - ws "^6.0.0" - -webpack-cli@^3.3.12: - version "3.3.12" - resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-3.3.12.tgz#94e9ada081453cd0aa609c99e500012fd3ad2d4a" - integrity sha512-NVWBaz9k839ZH/sinurM+HcDvJOTXwSjYp1ku+5XKeOC03z8v5QitnK/x+lAxGXFyhdayoIf/GOpv85z3/xPag== - dependencies: - chalk "^2.4.2" - cross-spawn "^6.0.5" - enhanced-resolve "^4.1.1" - findup-sync "^3.0.0" - global-modules "^2.0.0" - import-local "^2.0.0" - interpret "^1.4.0" - loader-utils "^1.4.0" - supports-color "^6.1.0" - v8-compile-cache "^2.1.1" - yargs "^13.3.2" - -webpack-dev-middleware@^3.7.2: - version "3.7.2" - resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-3.7.2.tgz#0019c3db716e3fa5cecbf64f2ab88a74bab331f3" - dependencies: - memory-fs "^0.4.1" - mime "^2.4.4" - mkdirp "^0.5.1" - range-parser "^1.2.1" - webpack-log "^2.0.0" - -webpack-dev-server@^3.11.0: - version "3.11.0" - resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-3.11.0.tgz#8f154a3bce1bcfd1cc618ef4e703278855e7ff8c" - integrity sha512-PUxZ+oSTxogFQgkTtFndEtJIPNmml7ExwufBZ9L2/Xyyd5PnOL5UreWe5ZT7IU25DSdykL9p1MLQzmLh2ljSeg== - dependencies: - ansi-html "0.0.7" - bonjour "^3.5.0" - chokidar "^2.1.8" - compression "^1.7.4" - connect-history-api-fallback "^1.6.0" - debug "^4.1.1" - del "^4.1.1" - express "^4.17.1" - html-entities "^1.3.1" - http-proxy-middleware "0.19.1" - import-local "^2.0.0" - internal-ip "^4.3.0" - ip "^1.1.5" - is-absolute-url "^3.0.3" - killable "^1.0.1" - loglevel "^1.6.8" - opn "^5.5.0" - p-retry "^3.0.1" - portfinder "^1.0.26" - schema-utils "^1.0.0" - selfsigned "^1.10.7" - semver "^6.3.0" - serve-index "^1.9.1" - sockjs "0.3.20" - sockjs-client "1.4.0" - spdy "^4.0.2" - strip-ansi "^3.0.1" - supports-color "^6.1.0" - url "^0.11.0" - webpack-dev-middleware "^3.7.2" - webpack-log "^2.0.0" - ws "^6.2.1" - yargs "^13.3.2" - -webpack-log@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/webpack-log/-/webpack-log-2.0.0.tgz#5b7928e0637593f119d32f6227c1e0ac31e1b47f" - dependencies: - ansi-colors "^3.0.0" - uuid "^3.3.2" +webidl-conversions@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a" + integrity sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g== -webpack-sources@^1.0.0, webpack-sources@^1.4.0, webpack-sources@^1.4.1, webpack-sources@^1.4.3: - version "1.4.3" - resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.4.3.tgz#eedd8ec0b928fbf1cbfe994e22d2d890f330a933" - integrity sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ== - dependencies: - source-list-map "^2.0.0" - source-map "~0.6.1" - -webpack@^4.43.0: - version "4.43.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.43.0.tgz#c48547b11d563224c561dad1172c8aa0b8a678e6" - integrity sha512-GW1LjnPipFW2Y78OOab8NJlCflB7EFskMih2AHdvjbpKMeDJqEgSx24cXXXiPS65+WSwVyxtDsJH6jGX2czy+g== - dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/helper-module-context" "1.9.0" - "@webassemblyjs/wasm-edit" "1.9.0" - "@webassemblyjs/wasm-parser" "1.9.0" - acorn "^6.4.1" - ajv "^6.10.2" - ajv-keywords "^3.4.1" - chrome-trace-event "^1.0.2" - enhanced-resolve "^4.1.0" - eslint-scope "^4.0.3" - json-parse-better-errors "^1.0.2" - loader-runner "^2.4.0" - loader-utils "^1.2.3" - memory-fs "^0.4.1" - micromatch "^3.1.10" - mkdirp "^0.5.3" - neo-async "^2.6.1" - node-libs-browser "^2.2.1" - schema-utils "^1.0.0" - tapable "^1.1.3" - terser-webpack-plugin "^1.4.3" - watchpack "^1.6.1" - webpack-sources "^1.4.1" - -websocket-driver@0.6.5: - version "0.6.5" - resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.6.5.tgz#5cb2556ceb85f4373c6d8238aa691c8454e13a36" - integrity sha1-XLJVbOuF9Dc8bYI4qmkchFThOjY= - dependencies: - websocket-extensions ">=0.1.1" - -websocket-driver@>=0.5.1: - version "0.7.0" - resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.0.tgz#0caf9d2d755d93aee049d4bdd0d3fe2cca2a24eb" +whatwg-encoding@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz#e7635f597fd87020858626805a2729fa7698ac53" + integrity sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg== dependencies: - http-parser-js ">=0.4.0" - websocket-extensions ">=0.1.1" - -websocket-extensions@>=0.1.1: - version "0.1.3" - resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.3.tgz#5d2ff22977003ec687a4b87073dfbbac146ccf29" + iconv-lite "0.6.3" -whatwg-mimetype@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf" - integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g== +whatwg-mimetype@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz#5fa1a7623867ff1af6ca3dc72ad6b8a4208beba7" + integrity sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q== whatwg-url@^5.0.0: version "5.0.0" @@ -9425,16 +4722,6 @@ which-boxed-primitive@^1.0.2: is-string "^1.0.5" is-symbol "^1.0.3" -which-module@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" - -which@^1.2.14, which@^1.2.9, which@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" - dependencies: - isexe "^2.0.0" - which@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" @@ -9442,70 +4729,27 @@ which@^2.0.1: dependencies: isexe "^2.0.0" -wide-align@^1.1.0: - version "1.1.3" - resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" - dependencies: - string-width "^1.0.2 || 2" - word-wrap@^1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== -worker-farm@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.7.0.tgz#26a94c5391bbca926152002f69b84a4bf772e5a8" - dependencies: - errno "~0.1.7" - -wrap-ansi@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09" - dependencies: - ansi-styles "^3.2.0" - string-width "^3.0.0" - strip-ansi "^5.0.0" - -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== -write-file-atomic@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-4.0.1.tgz#9faa33a964c1c85ff6f849b80b42a88c2c537c8f" - integrity sha512-nSKUxgAbyioruk6hU87QzVbY279oYT6uiwgDoujth2ju4mJ+TZau7SQBhtbTmUyuNYTuXnSyRn66FV0+eCgcrQ== - dependencies: - imurmurhash "^0.1.4" - signal-exit "^3.0.7" - -ws@^6.0.0, ws@^6.2.1: - version "6.2.1" - resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb" - dependencies: - async-limiter "~1.0.0" - -xtend@^4.0.0, xtend@^4.0.2, xtend@~4.0.1: +xtend@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== -xterm-addon-fit@^0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/xterm-addon-fit/-/xterm-addon-fit-0.5.0.tgz#2d51b983b786a97dcd6cde805e700c7f913bc596" - integrity sha512-DsS9fqhXHacEmsPxBJZvfj2la30Iz9xk+UKjhQgnYNkrUIN5CYLbw7WEfz117c7+S86S/tpHPfvNxJsF5/G8wQ== +xterm-addon-fit@0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/xterm-addon-fit/-/xterm-addon-fit-0.6.0.tgz#142e1ce181da48763668332593fc440349c88c34" + integrity sha512-9/7A+1KEjkFam0yxTaHfuk9LEvvTSBi0PZmEkzJqgafXPEXL9pCMAVV7rB09sX6ATRDXAdBpQhZkhKj7CGvYeg== -xterm-addon-search-bar@^0.2.0: +xterm-addon-search-bar@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/xterm-addon-search-bar/-/xterm-addon-search-bar-0.2.0.tgz#e03c020a5ed22f1e8d503946b26a14ade508bc91" integrity sha512-xvXmBA/ShbnzGe5CCy0kqPNNGqjkpuaRgH3Z1iW0V71vCAPRrtJ/v/hMnysZBH7WGUYhlCQr1cJZagW2fBVvSg== @@ -9513,33 +4757,20 @@ xterm-addon-search-bar@^0.2.0: babel-runtime "^6.26.0" rxjs-compat "^6.5.4" -xterm-addon-search@^0.9.0: - version "0.9.0" - resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.9.0.tgz#95278ebb818cfcf882209ae75be96e0bea5d52a5" - integrity sha512-aoolI8YuHvdGw+Qjg8g2M4kst0v86GtB7WeBm4F0jNXA005/6QbWWy9eCsvnIDLJOFI5JSSrZnD6CaOkvBQYPA== - -xterm-addon-web-links@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/xterm-addon-web-links/-/xterm-addon-web-links-0.6.0.tgz#0296cb6c99588847894670d998c9ea6a6aeb26ee" - integrity sha512-H6XzjWWZu8FBo+fnYpxdPk9w5M6drbsvwPEJZGRS38MihiQaVFpKlCMKdfRgDbKGE530tw1yH54rhpZfHgt2/A== - -xterm@^4.19.0: - version "4.19.0" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.19.0.tgz#c0f9d09cd61de1d658f43ca75f992197add9ef6d" - integrity sha512-c3Cp4eOVsYY5Q839dR5IejghRPpxciGmLWWaP9g+ppfMeBChMeLa1DCA+pmX/jyDZ+zxFOmlJL/82qVdayVoGQ== - -y18n@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" +xterm-addon-search@0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.10.0.tgz#b6a5e859c0bfd83ad534233f93376640c0e0c652" + integrity sha512-l+kjDxNDQbkniU5OUo9BHknxUEPZGM0OFpVpc2sMmrb97S0FKJVJO4wAZPJvSGVJ8ZEG6KuDyzXluvnb08t71Q== -y18n@^5.0.5: - version "5.0.8" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" - integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== +xterm-addon-web-links@0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/xterm-addon-web-links/-/xterm-addon-web-links-0.7.0.tgz#dceac36170605f9db10a01d716bd83ee38f65c17" + integrity sha512-6PqoqzzPwaeSq22skzbvyboDvSnYk5teUYEoKBwMYvhbkwOQkemZccjWHT5FnNA8o1aInTc4PRYAl4jjPucCKA== -yallist@^3.0.0, yallist@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.2.tgz#8452b4bb7e83c7c188d8041c1a837c773d6d8bb9" +xterm@5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.0.0.tgz#0af50509b33d0dc62fde7a4ec17750b8e453cc5c" + integrity sha512-tmVsKzZovAYNDIaUinfz+VDclraQpPUnAME+JawosgWRMphInDded/PuY0xmU5dOhyeYZsI0nz5yd8dPYsdLTA== yallist@^4.0.0: version "4.0.0" @@ -9551,70 +4782,15 @@ yaml@^1.10.0, yaml@^1.10.2, yaml@^1.7.2: resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== -yargs-parser@^13.1.2: - version "13.1.2" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38" - integrity sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg== - dependencies: - camelcase "^5.0.0" - decamelize "^1.2.0" - -yargs-parser@^21.0.0, yargs-parser@^21.0.1: - version "21.0.1" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.0.1.tgz#0267f286c877a4f0f728fceb6f8a3e4cb95c6e35" - integrity sha512-9BK1jFpLzJROCI5TzwZL/TU4gqjK5xiHV/RfWLOahrjAko/e4DJkRDZQXfvqAsiZzzYhgAzbgz6lg48jcm4GLg== - -yargs@^13.3.2: - version "13.3.2" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd" - integrity sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw== - dependencies: - cliui "^5.0.0" - find-up "^3.0.0" - get-caller-file "^2.0.1" - require-directory "^2.1.1" - require-main-filename "^2.0.0" - set-blocking "^2.0.0" - string-width "^3.0.0" - which-module "^2.0.0" - y18n "^4.0.0" - yargs-parser "^13.1.2" - -yargs@^17.3.1: - version "17.5.1" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.5.1.tgz#e109900cab6fcb7fd44b1d8249166feb0b36e58e" - integrity sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA== - dependencies: - cliui "^7.0.2" - escalade "^3.1.1" - get-caller-file "^2.0.5" - require-directory "^2.1.1" - string-width "^4.2.3" - y18n "^5.0.5" - yargs-parser "^21.0.0" - -yarn-deduplicate@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/yarn-deduplicate/-/yarn-deduplicate-1.1.1.tgz#19b4a87654b66f55bf3a4bd6b153b4e4ab1b6e6d" - dependencies: - "@yarnpkg/lockfile" "^1.1.0" - commander "^2.10.0" - semver "^5.3.0" - -yocto-queue@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" - integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== - -yup@^0.29.1: - version "0.29.1" - resolved "https://registry.yarnpkg.com/yup/-/yup-0.29.1.tgz#35d25aab470a0c3950f66040ba0ff4b1b6efe0d9" - integrity sha512-U7mPIbgfQWI6M3hZCJdGFrr+U0laG28FxMAKIgNvgl7OtyYuUoc4uy9qCWYHZjh49b8T7Ug8NNDdiMIEytcXrQ== - dependencies: - "@babel/runtime" "^7.9.6" - fn-name "~3.0.0" - lodash "^4.17.15" - lodash-es "^4.17.11" - property-expr "^2.0.2" - synchronous-promise "^2.0.10" +yup@0.32.11: + version "0.32.11" + resolved "https://registry.yarnpkg.com/yup/-/yup-0.32.11.tgz#d67fb83eefa4698607982e63f7ca4c5ed3cf18c5" + integrity sha512-Z2Fe1bn+eLstG8DRR6FTavGD+MeAwyfmouhHsIUgaADz8jvFKbO/fXc2trJKZg+5EBjh4gGm3iU/t3onKlXHIg== + dependencies: + "@babel/runtime" "^7.15.4" + "@types/lodash" "^4.14.175" + lodash "^4.17.21" + lodash-es "^4.17.21" + nanoclone "^0.2.1" + property-expr "^2.0.4" toposort "^2.0.2" From 3bf5a718021fcf4a08699f83a1829b502c54a662 Mon Sep 17 00:00:00 2001 From: Lance Pioch Date: Fri, 25 Nov 2022 15:29:04 -0500 Subject: [PATCH 284/458] PostgreSQL Support (#4486) Co-authored-by: Matthew Penner --- .env.ci | 20 --- .github/workflows/ci.yaml | 115 ++++++++++++++++-- .github/workflows/lint.yaml | 3 - .../Api/Client/ClientController.php | 5 + app/Models/Node.php | 4 +- app/Models/User.php | 4 +- app/Providers/AppServiceProvider.php | 4 +- app/Providers/HashidsServiceProvider.php | 9 +- app/Providers/RepositoryServiceProvider.php | 2 +- app/Providers/RouteServiceProvider.php | 2 +- app/Providers/SettingsServiceProvider.php | 2 +- .../Eloquent/AllocationRepository.php | 6 +- .../Eloquent/EloquentRepository.php | 15 ++- app/Repositories/Eloquent/NodeRepository.php | 6 +- .../Deployment/FindViableNodesService.php | 8 +- bootstrap/tests.php | 2 + config/database.php | 15 +++ ...016_01_23_195641_add_allocations_table.php | 5 +- .../2016_01_23_195851_add_api_keys.php | 5 +- .../2016_01_23_200044_add_api_permissions.php | 5 +- .../2016_01_23_200159_add_downloads.php | 5 +- ..._01_23_200421_create_failed_jobs_table.php | 5 +- .../2016_01_23_200440_create_jobs_table.php | 6 +- .../2016_01_23_200528_add_locations.php | 5 +- .../2016_01_23_200648_add_nodes.php | 7 +- .../2016_01_23_201433_add_password_resets.php | 5 +- .../2016_01_23_201531_add_permissions.php | 5 +- ...2016_01_23_201649_add_server_variables.php | 5 +- .../2016_01_23_201748_add_servers.php | 5 +- .../2016_01_23_202544_add_service_options.php | 5 +- ...2016_01_23_202731_add_service_varibles.php | 5 +- .../2016_01_23_202943_add_services.php | 5 +- ...016_01_23_203119_create_settings_table.php | 5 +- .../2016_01_23_203150_add_subusers.php | 5 +- .../2016_01_23_203159_add_users.php | 5 +- ...016_01_23_203947_create_sessions_table.php | 5 +- ...01_25_234418_rename_permissions_column.php | 5 +- ...2016_02_07_172148_add_databases_tables.php | 5 +- ...2_07_181319_add_database_servers_table.php | 5 +- ...306_add_service_option_default_startup.php | 5 +- ..._02_20_155318_add_unique_service_field.php | 7 +- .../2016_02_27_163411_add_tasks_table.php | 5 +- .../2016_02_27_163447_add_tasks_log_table.php | 5 +- ...3_18_155649_add_nullable_field_lastrun.php | 24 ---- .../2016_08_30_212718_add_ip_alias.php | 5 +- ..._08_30_213301_modify_ip_storage_method.php | 5 +- ...9_01_193520_add_suspension_for_servers.php | 5 +- ...2016_09_01_211924_remove_active_column.php | 5 +- ...09_02_190647_add_sftp_password_storage.php | 5 +- .../2016_09_04_171338_update_jobs_tables.php | 10 +- ..._09_04_172028_update_failed_jobs_table.php | 4 +- ...9_04_182835_create_notifications_table.php | 5 +- ...016_09_07_163017_add_unique_identifier.php | 4 +- ..._09_14_145945_allow_longer_regex_field.php | 4 +- ...6_09_17_194246_add_docker_image_column.php | 5 +- ...9_21_165554_update_servers_column_name.php | 4 +- ..._09_29_213518_rename_double_insurgency.php | 4 +- .../2016_10_07_152117_build_api_log_table.php | 4 +- .../2016_10_14_164802_update_api_keys.php | 4 +- ...16_10_23_181719_update_misnamed_bungee.php | 4 +- ..._10_23_193810_add_foreign_keys_servers.php | 58 ++++----- ...6_10_23_201624_add_foreign_allocations.php | 23 ++-- ...2016_10_23_202222_add_foreign_api_keys.php | 8 +- ..._23_202703_add_foreign_api_permissions.php | 15 ++- ...23_202953_add_foreign_database_servers.php | 8 +- ...016_10_23_203105_add_foreign_databases.php | 12 +- .../2016_10_23_203335_add_foreign_nodes.php | 15 ++- ...6_10_23_203522_add_foreign_permissions.php | 12 +- ...23_203857_add_foreign_server_variables.php | 18 +-- ..._23_204157_add_foreign_service_options.php | 15 ++- ...3_204321_add_foreign_service_variables.php | 15 ++- ...2016_10_23_204454_add_foreign_subusers.php | 12 +- .../2016_10_23_204610_add_foreign_tasks.php | 4 +- ...04_000949_add_ark_service_option_fixed.php | 4 +- .../2016_11_11_220649_add_pack_support.php | 4 +- ...6_11_11_231731_set_service_name_unique.php | 4 +- .../2016_11_27_142519_add_pack_column.php | 4 +- ...1_173018_add_configurable_upload_limit.php | 4 +- ...12_02_185206_correct_service_variables.php | 4 +- ...7_01_03_150436_fix_misnamed_option_tag.php | 4 +- ...create_node_configuration_tokens_table.php | 4 +- .../2017_01_12_135449_add_more_user_data.php | 4 +- .../2017_02_02_175548_UpdateColumnNames.php | 29 ++--- .../2017_02_03_140948_UpdateNodesTable.php | 10 +- .../2017_02_03_155554_RenameColumns.php | 18 ++- .../2017_02_05_164123_AdjustColumnNames.php | 11 +- ...64516_AdjustColumnNamesForServicePacks.php | 11 +- ...2_09_174834_SetupPermissionsPivotTable.php | 18 ++- ...7_02_10_171858_UpdateAPIKeyColumnNames.php | 8 +- ...3_224254_UpdateNodeConfigTokensColumns.php | 4 +- ...5_212803_DeleteServiceExecutableOption.php | 4 +- ..._10_162934_AddNewServiceOptionsColumns.php | 4 +- ...03_10_173607_MigrateToNewServiceSystem.php | 4 +- ..._ChangeServiceVariablesValidationRules.php | 4 +- ...150648_MoveFunctionsFromFileToDatabase.php | 4 +- ...5631_RenameServicePacksToSingluarPacks.php | 4 +- ...17_03_14_200326_AddLockedStatusToTable.php | 4 +- ...eOrganizeDatabaseServersToDatabaseHost.php | 4 +- ..._03_16_181515_CleanupDatabasesDatabase.php | 4 +- ...2017_03_18_204953_AddForeignKeyToPacks.php | 4 +- ...3_31_221948_AddServerDescriptionColumn.php | 4 +- ..._163232_DropDeletedAtColumnFromServers.php | 4 +- .../2017_04_15_125021_UpgradeTaskSystem.php | 4 +- ...4_20_171943_AddScriptsToServiceOptions.php | 4 +- ...1432_AddServiceScriptTrackingToServers.php | 4 +- ...7_04_27_145300_AddCopyScriptFromColumn.php | 4 +- ...ConnectionOverSSLWithDaemonBehindProxy.php | 4 +- .../2017_05_01_141528_DeleteDownloadTable.php | 4 +- ...01_141559_DeleteNodeConfigurationTable.php | 4 +- ..._06_10_152951_add_external_id_to_users.php | 4 +- ...23_ChangeForeignKeyToBeOnCascadeDelete.php | 4 +- ...eUserPermissionsToDeleteOnUserDeletion.php | 4 +- ...llocationToReferenceNullOnServerDelete.php | 4 +- ...DeletionWhenAServerOrVariableIsDeleted.php | 4 +- ...33_DeleteTaskWhenParentServerIsDeleted.php | 4 +- ...ValuesForDatabaseHostWhenNodeIsDeleted.php | 4 +- ...4_AllowNegativeValuesForOverallocation.php | 6 +- ...SetAllocationUnqiueUsingMultipleFields.php | 4 +- ...adeDeletionWhenAParentServiceIsDeleted.php | 4 +- ...vePackWhenParentServiceOptionIsDeleted.php | 4 +- ...9_RenameTasksTableForStructureRefactor.php | 4 +- ...2017_09_10_225941_CreateSchedulesTable.php | 4 +- ...230309_CreateNewTasksTableForSchedules.php | 4 +- ..._002938_TransferOldTasksToNewScheduler.php | 63 +++++----- ...dPermissionsToPointToNewScheduleSystem.php | 4 +- ...017_09_23_170933_CreateDaemonKeysTable.php | 4 +- ...628_RemoveDaemonSecretFromServersTable.php | 4 +- ...22_RemoveDaemonSecretFromSubusersTable.php | 5 +- ...angeServicesToUseAMoreUniqueIdentifier.php | 4 +- ...ngeToABetterUniqueServiceConfiguration.php | 4 +- ...cadeDeletionWhenServiceOptionIsDeleted.php | 4 +- ...10_06_214026_ServicesToNestsConversion.php | 5 +- ..._214053_ServiceOptionsToEggsConversion.php | 5 +- ...rviceVariablesToEggVariablesConversion.php | 5 +- ..._24_222238_RemoveLegacySFTPInformation.php | 4 +- ...1922_Add2FaLastAuthorizationTimeColumn.php | 4 +- ...122708_MigratePubPrivFormatToSingleKey.php | 29 +++-- ...84012_DropAllocationsWhenNodeIsDeleted.php | 4 +- ...220426_MigrateSettingsTableToNewFormat.php | 4 +- ...22821_AllowNegativeValuesForServerSwap.php | 8 +- ...1_11_213943_AddApiKeyPermissionColumns.php | 8 +- ...1_13_142012_SetupTableForKeyEncryption.php | 8 +- .../2018_01_13_145209_AddLastUsedAtColumn.php | 8 +- ...02_04_145617_AllowTextInUserExternalId.php | 8 +- ...ove_unique_index_on_external_id_column.php | 8 +- ..._unique_allocation_id_on_servers_table.php | 8 +- ...dd_external_id_column_to_servers_table.php | 8 +- ...152_remove_default_null_value_on_table.php | 6 +- ...fine_unique_index_on_users_external_id.php | 8 +- ...nd_port_limit_columns_to_servers_table.php | 8 +- ..._03_15_124536_add_description_to_nodes.php | 8 +- ..._05_04_123826_add_maintenance_to_nodes.php | 8 +- ...ow_egg_variables_to_have_longer_values.php | 8 +- ...server_variables_to_have_longer_values.php | 8 +- ...2328_set_allocation_limit_default_null.php | 8 +- ...1_fix_unique_index_to_account_for_host.php | 8 +- ..._merge_permissions_table_into_subusers.php | 15 +-- ...20_03_22_164814_drop_permissions_table.php | 8 +- ...24_add_threads_column_to_servers_table.php | 8 +- ...2020_04_03_230614_create_backups_table.php | 8 +- ...4_04_131016_add_table_server_transfers.php | 14 +-- ...4_store_node_tokens_as_encrypted_value.php | 11 +- ..._17_203438_allow_nullable_descriptions.php | 8 +- ...4_22_055500_add_max_connections_column.php | 8 +- ..._26_111208_add_backup_limit_to_servers.php | 8 +- .../2020_05_20_234655_add_mounts_table.php | 8 +- ...20_05_21_192756_add_mount_server_table.php | 8 +- ...3612_create_user_recovery_tokens_table.php | 8 +- ...01845_add_notes_column_for_allocations.php | 8 +- ...533_add_backup_state_column_to_backups.php | 8 +- ...132500_update_bytes_to_unsigned_bigint.php | 8 +- ...31_modify_checksums_column_for_backups.php | 8 +- ...0_09_13_110007_drop_packs_from_servers.php | 8 +- ...21_drop_packs_from_api_key_permissions.php | 8 +- .../2020_09_13_110047_drop_packs_table.php | 8 +- ...020_09_13_113503_drop_daemon_key_table.php | 8 +- ...ue_database_name_to_account_for_server.php | 8 +- ...move_nullable_from_schedule_name_field.php | 8 +- ..._02_201014_add_features_column_to_eggs.php | 8 +- ...ort_multiple_docker_images_and_updates.php | 31 +++-- ...uccessful_nullable_in_server_transfers.php | 8 +- ...chived_field_to_server_transfers_table.php | 12 +- ..._24_092449_make_allocation_fields_json.php | 34 ------ ..._add_upload_id_column_to_backups_table.php | 8 +- ...53937_add_file_denylist_to_egg_configs.php | 8 +- .../2021_01_13_013420_add_cron_month.php | 8 +- ...1_01_17_102401_create_audit_logs_table.php | 8 +- ...52623_add_generic_server_status_column.php | 24 ++-- ...26_210502_update_file_denylist_to_json.php | 8 +- ...205021_add_index_for_server_and_action.php | 8 +- ..._23_212657_make_sftp_port_unsigned_int.php | 8 +- ...n_month_field_to_have_value_if_missing.php | 14 +-- ...dd_continue_on_failure_option_to_tasks.php | 8 +- ...when_server_online_option_to_schedules.php | 8 +- ...01016_add_support_for_locking_a_backup.php | 8 +- ...21_07_12_013420_remove_userinteraction.php | 39 ++++-- ...7_17_211512_create_user_ssh_keys_table.php | 4 +- ...d_to_default_to_false_on_backups_table.php | 8 +- ...1_add_foreign_keys_to_mount_node_table.php | 8 +- ...add_foreign_keys_to_mount_server_table.php | 8 +- ...21_add_foreign_keys_to_egg_mount_table.php | 8 +- ...022_01_25_030847_drop_google_analytics.php | 14 +-- ...migrate_egg_images_array_to_new_format.php | 6 +- ...5_28_135717_create_activity_logs_table.php | 8 +- ...40349_create_activity_log_actors_table.php | 8 +- ...rack_api_key_usage_for_activity_events.php | 8 +- ...force_outgoing_ip_column_to_eggs_table.php | 8 +- ...d_installed_at_column_to_servers_table.php | 8 +- ...files_column_nullable_on_backups_table.php | 27 ++++ ...ix_language_column_type_on_users_table.php | 36 ++++++ phpunit.xml | 2 +- .../Location/LocationControllerTest.php | 2 +- .../Application/Nests/EggControllerTest.php | 2 +- .../Application/Nests/NestControllerTest.php | 2 +- .../Users/ExternalUserControllerTest.php | 2 +- .../Application/Users/UserControllerTest.php | 2 +- .../Api/Client/ClientControllerTest.php | 26 ++-- .../Startup/GetStartupAndVariablesTest.php | 2 +- tests/Integration/IntegrationTestCase.php | 2 +- .../Servers/ServerCreationServiceTest.php | 7 +- .../StartupModificationServiceTest.php | 19 +-- .../Servers/VariableValidatorServiceTest.php | 11 +- tests/Traits/MocksPdoConnection.php | 48 -------- 223 files changed, 913 insertions(+), 1053 deletions(-) delete mode 100644 .env.ci delete mode 100644 database/migrations/2016_03_18_155649_add_nullable_field_lastrun.php delete mode 100644 database/migrations/2020_12_24_092449_make_allocation_fields_json.php create mode 100644 database/migrations/2022_11_01_163744_make_ignored_files_column_nullable_on_backups_table.php create mode 100644 database/migrations/2022_11_01_165830_fix_language_column_type_on_users_table.php delete mode 100644 tests/Traits/MocksPdoConnection.php diff --git a/.env.ci b/.env.ci deleted file mode 100644 index 1a9e848e3b..0000000000 --- a/.env.ci +++ /dev/null @@ -1,20 +0,0 @@ -APP_ENV=testing -APP_DEBUG=true -APP_KEY=SomeRandomString3232RandomString -APP_THEME=pterodactyl -APP_TIMEZONE=UTC -APP_URL=http://localhost/ -APP_ENVIRONMENT_ONLY=true - -DB_CONNECTION=mysql -DB_HOST=127.0.0.1 -DB_DATABASE=testing -DB_USERNAME=root -DB_PASSWORD= - -CACHE_DRIVER=array -SESSION_DRIVER=array -MAIL_DRIVER=array -QUEUE_DRIVER=sync - -HASHIDS_SALT=test123 diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 4da19b6ed2..28ce63e4a7 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -11,23 +11,43 @@ on: - "1.0-develop" jobs: - tests: - name: Tests + mysql: + name: MySQL runs-on: ubuntu-20.04 strategy: fail-fast: false matrix: - php: [8.0, 8.1] - database: ["mariadb:10.2", "mysql:8"] + php: [8.1] + database: ["mariadb:10.2", "mariadb:10.9", "mysql:8"] services: database: - image: ${{ matrix.database }} + image: docker.io/library/${{ matrix.database }} env: MYSQL_ALLOW_EMPTY_PASSWORD: yes MYSQL_DATABASE: testing ports: - - 3306 + - 3306/tcp options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 + env: + APP_ENV: testing + APP_DEBUG: "true" + APP_KEY: SomeRandomString3232RandomString + APP_THEME: pterodactyl + APP_TIMEZONE: UTC + APP_URL: http://localhost/ + APP_ENVIRONMENT_ONLY: "true" + + DB_CONNECTION: mysql + DB_HOST: 127.0.0.1 + DB_DATABASE: testing + DB_USERNAME: root + + CACHE_DRIVER: array + MAIL_MAILER: array + SESSION_DRIVER: array + QUEUE_CONNECTION: sync + + HASHIDS_SALT: test123 steps: - name: Code Checkout uses: actions/checkout@v3 @@ -53,9 +73,6 @@ jobs: tools: composer:v2 coverage: none - - name: Setup .env - run: cp .env.ci .env - - name: Install dependencies run: composer install --no-interaction --no-progress --no-suggest --prefer-dist @@ -69,4 +86,82 @@ jobs: run: vendor/bin/phpunit tests/Integration env: DB_PORT: ${{ job.services.database.ports[3306] }} - DB_USERNAME: root + + postgres: + name: PostgreSQL + runs-on: ubuntu-20.04 + if: "!contains(github.event.head_commit.message, 'skip ci') && !contains(github.event.head_commit.message, 'ci skip')" + strategy: + fail-fast: false + matrix: + php: [8.1] + database: ["postgres:13", "postgres:14", "postgres:15"] + services: + database: + image: docker.io/library/${{ matrix.database }} + env: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: testing + ports: + - 5432/tcp + options: --health-cmd=pg_isready --health-interval=10s --health-timeout=5s --health-retries=3 + env: + APP_ENV: testing + APP_DEBUG: "true" + APP_KEY: SomeRandomString3232RandomString + APP_THEME: pterodactyl + APP_TIMEZONE: UTC + APP_URL: http://localhost/ + APP_ENVIRONMENT_ONLY: "true" + + DB_CONNECTION: pgsql + DB_HOST: 127.0.0.1 + DB_DATABASE: testing + DB_USERNAME: postgres + DB_PASSWORD: postgres + + CACHE_DRIVER: array + MAIL_MAILER: array + SESSION_DRIVER: array + QUEUE_CONNECTION: sync + + HASHIDS_SALT: test123 + steps: + - name: Code Checkout + uses: actions/checkout@v3 + + - name: Get cache directory + id: composer-cache + run: | + echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Cache + uses: actions/cache@v3 + with: + path: | + ~/.php_cs.cache + ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-cache-${{ matrix.php }}-${{ hashFiles('**.composer.lock') }} + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + extensions: bcmath, cli, curl, gd, mbstring, mysql, openssl, pdo, tokenizer, xml, zip + tools: composer:v2 + coverage: none + + - name: Install dependencies + run: composer install --no-interaction --no-progress --no-suggest --prefer-dist + + - name: Unit tests + run: vendor/bin/phpunit --bootstrap vendor/autoload.php tests/Unit + if: ${{ always() }} + env: + DB_HOST: UNIT_NO_DB + + - name: Integration tests + run: vendor/bin/phpunit tests/Integration + env: + DB_PORT: ${{ job.services.database.ports[5432] }} diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 4ac292b686..a48c6b14ed 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -26,9 +26,6 @@ jobs: tools: composer:v2 coverage: none - - name: Setup .env - run: cp .env.ci .env - - name: Install dependencies run: composer install --no-interaction --no-progress --no-suggest --prefer-dist diff --git a/app/Http/Controllers/Api/Client/ClientController.php b/app/Http/Controllers/Api/Client/ClientController.php index 9afb72628f..dcdb5964bc 100644 --- a/app/Http/Controllers/Api/Client/ClientController.php +++ b/app/Http/Controllers/Api/Client/ClientController.php @@ -40,6 +40,11 @@ public function index(GetServersRequest $request): array AllowedFilter::custom('*', new MultiFieldServerFilter()), ]); + $loweredBindings = collect($builder->getBindings()) + ->map(fn ($f, $key) => is_string($f) ? strtolower($f) : $f) + ->all(); + $builder->setBindings($loweredBindings); + $type = $request->input('type'); // Either return all the servers the user has access to because they are an admin `?type=admin` or // just return all the servers the user has access to because they are the owner or a subuser of the diff --git a/app/Models/Node.php b/app/Models/Node.php index 62ec828712..504a28c248 100644 --- a/app/Models/Node.php +++ b/app/Models/Node.php @@ -225,8 +225,8 @@ public function allocations(): HasMany */ public function isViable(int $memory, int $disk): bool { - $memoryLimit = $this->memory * (1 + ($this->memory_overallocate / 100)); - $diskLimit = $this->disk * (1 + ($this->disk_overallocate / 100)); + $memoryLimit = $this->memory * (1.0 + ($this->memory_overallocate / 100.0)); + $diskLimit = $this->disk * (1.0 + ($this->disk_overallocate / 100.0)); return ($this->sum_memory + $memory) <= $memoryLimit && ($this->sum_disk + $disk) <= $diskLimit; } diff --git a/app/Models/User.php b/app/Models/User.php index df8271cf4f..eb3c15d220 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -76,7 +76,9 @@ * @method static Builder|User whereUsername($value) * @method static Builder|User whereUuid($value) * - * @mixin \Eloquent + * @mixin \Barryvdh\LaravelIdeHelper\Eloquent + * @mixin \Illuminate\Database\Query\Builder + * @mixin \Illuminate\Database\Eloquent\Builder */ class User extends Model implements AuthenticatableContract, diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index d4ffdadbbd..6a805bad38 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -2,12 +2,12 @@ namespace Pterodactyl\Providers; -use View; -use Cache; use Pterodactyl\Models; use Illuminate\Support\Str; use Illuminate\Support\Facades\URL; use Illuminate\Pagination\Paginator; +use Illuminate\Support\Facades\View; +use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Schema; use Illuminate\Support\ServiceProvider; use Pterodactyl\Extensions\Themes\Theme; diff --git a/app/Providers/HashidsServiceProvider.php b/app/Providers/HashidsServiceProvider.php index f094878d0f..4e48208ef6 100644 --- a/app/Providers/HashidsServiceProvider.php +++ b/app/Providers/HashidsServiceProvider.php @@ -14,13 +14,10 @@ class HashidsServiceProvider extends ServiceProvider public function register() { $this->app->singleton(HashidsInterface::class, function () { - /** @var \Illuminate\Contracts\Config\Repository $config */ - $config = $this->app['config']; - return new Hashids( - $config->get('hashids.salt', ''), - $config->get('hashids.length', 0), - $config->get('hashids.alphabet', 'abcdefghijkmlnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890') + config('hashids.salt', ''), + config('hashids.length', 0), + config('hashids.alphabet', 'abcdefghijkmlnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890') ); }); diff --git a/app/Providers/RepositoryServiceProvider.php b/app/Providers/RepositoryServiceProvider.php index 8a0434f524..e5a16dd509 100644 --- a/app/Providers/RepositoryServiceProvider.php +++ b/app/Providers/RepositoryServiceProvider.php @@ -41,7 +41,7 @@ class RepositoryServiceProvider extends ServiceProvider { /** - * Register all of the repository bindings. + * Register all the repository bindings. */ public function register() { diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index f5ac5565d5..25f18c5241 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -30,7 +30,7 @@ public function boot() }); // This is needed to make use of the "resolveRouteBinding" functionality in the - // model. Without it you'll never trigger that logic flow thus resulting in a 404 + // model. Without it, you'll never trigger that logic flow thus resulting in a 404 // error because we request databases with a HashID, and not with a normal ID. Route::model('database', Database::class); diff --git a/app/Providers/SettingsServiceProvider.php b/app/Providers/SettingsServiceProvider.php index 9c8a7445ec..e2bcdafe6f 100644 --- a/app/Providers/SettingsServiceProvider.php +++ b/app/Providers/SettingsServiceProvider.php @@ -80,7 +80,7 @@ public function boot(ConfigRepository $config, Encrypter $encrypter, Log $log, S if (in_array($key, self::$encrypted)) { try { $value = $encrypter->decrypt($value); - } catch (DecryptException $exception) { + } catch (DecryptException) { } } diff --git a/app/Repositories/Eloquent/AllocationRepository.php b/app/Repositories/Eloquent/AllocationRepository.php index 6eb8b6d1e5..01ed1c5beb 100644 --- a/app/Repositories/Eloquent/AllocationRepository.php +++ b/app/Repositories/Eloquent/AllocationRepository.php @@ -40,14 +40,14 @@ public function getUnassignedAllocationIds(int $node): array */ protected function getDiscardableDedicatedAllocations(array $nodes = []): array { - $query = Allocation::query()->selectRaw('CONCAT_WS("-", node_id, ip) as result'); + $query = Allocation::query()->selectRaw('CONCAT_WS(\'-\', node_id, ip) as result'); if (!empty($nodes)) { $query->whereIn('node_id', $nodes); } return $query->whereNotNull('server_id') - ->groupByRaw('CONCAT(node_id, ip)') + ->groupByRaw('result') ->get() ->pluck('result') ->toArray(); @@ -89,7 +89,7 @@ public function getRandomAllocation(array $nodes, array $ports, bool $dedicated if (!empty($discard)) { $query->whereNotIn( - $this->getBuilder()->raw('CONCAT_WS("-", node_id, ip)'), + $this->getBuilder()->raw('CONCAT_WS(\'-\', node_id, ip)'), $discard ); } diff --git a/app/Repositories/Eloquent/EloquentRepository.php b/app/Repositories/Eloquent/EloquentRepository.php index a4e7f2fd78..a78295be46 100644 --- a/app/Repositories/Eloquent/EloquentRepository.php +++ b/app/Repositories/Eloquent/EloquentRepository.php @@ -2,9 +2,12 @@ namespace Pterodactyl\Repositories\Eloquent; +use PDO; +use RuntimeException; use Illuminate\Http\Request; use Webmozart\Assert\Assert; use Illuminate\Support\Collection; +use Illuminate\Support\Facades\DB; use Illuminate\Database\Eloquent\Model; use Pterodactyl\Repositories\Repository; use Illuminate\Database\Eloquent\Builder; @@ -271,7 +274,17 @@ public function insertIgnore(array $values): bool return sprintf('(%s)', $grammar->parameterize($record)); })->implode(', '); - $statement = "insert ignore into $table ($columns) values $parameters"; + $driver = DB::getPdo()->getAttribute(PDO::ATTR_DRIVER_NAME); + switch ($driver) { + case 'mysql': + $statement = "insert ignore into $table ($columns) values $parameters"; + break; + case 'pgsql': + $statement = "insert into $table ($columns) values $parameters on conflict do nothing"; + break; + default: + throw new RuntimeException("Unsupported database driver \"$driver\" for insert ignore."); + } return $this->getBuilder()->getConnection()->statement($statement, $bindings); } diff --git a/app/Repositories/Eloquent/NodeRepository.php b/app/Repositories/Eloquent/NodeRepository.php index fe019e50a9..d7a3818f3d 100644 --- a/app/Repositories/Eloquent/NodeRepository.php +++ b/app/Repositories/Eloquent/NodeRepository.php @@ -22,7 +22,7 @@ public function model(): string public function getUsageStats(Node $node): array { $stats = $this->getBuilder() - ->selectRaw('IFNULL(SUM(servers.memory), 0) as sum_memory, IFNULL(SUM(servers.disk), 0) as sum_disk') + ->selectRaw('COALESCE(SUM(servers.memory), 0) as sum_memory, COALESCE(SUM(servers.disk), 0) as sum_disk') ->join('servers', 'servers.node_id', '=', 'nodes.id') ->where('node_id', '=', $node->id) ->first(); @@ -54,7 +54,7 @@ public function getUsageStats(Node $node): array public function getUsageStatsRaw(Node $node): array { $stats = $this->getBuilder()->select( - $this->getBuilder()->raw('IFNULL(SUM(servers.memory), 0) as sum_memory, IFNULL(SUM(servers.disk), 0) as sum_disk') + $this->getBuilder()->raw('COALESCE(SUM(servers.memory), 0) as sum_memory, COALESCE(SUM(servers.disk), 0) as sum_disk') )->join('servers', 'servers.node_id', '=', 'nodes.id')->where('node_id', $node->id)->first(); return collect(['disk' => $stats->sum_disk, 'memory' => $stats->sum_memory])->mapWithKeys(function ($value, $key) use ($node) { @@ -143,7 +143,7 @@ public function getNodeWithResourceUsage(int $node_id): Node { $instance = $this->getBuilder() ->select(['nodes.id', 'nodes.fqdn', 'nodes.scheme', 'nodes.daemon_token', 'nodes.daemonListen', 'nodes.memory', 'nodes.disk', 'nodes.memory_overallocate', 'nodes.disk_overallocate']) - ->selectRaw('IFNULL(SUM(servers.memory), 0) as sum_memory, IFNULL(SUM(servers.disk), 0) as sum_disk') + ->selectRaw('COALESCE(SUM(servers.memory), 0) as sum_memory, COALESCE(SUM(servers.disk), 0) as sum_disk') ->leftJoin('servers', 'servers.node_id', '=', 'nodes.id') ->where('nodes.id', $node_id); diff --git a/app/Services/Deployment/FindViableNodesService.php b/app/Services/Deployment/FindViableNodesService.php index 71c830bf9e..a95211c3f9 100644 --- a/app/Services/Deployment/FindViableNodesService.php +++ b/app/Services/Deployment/FindViableNodesService.php @@ -72,8 +72,8 @@ public function handle(int $perPage = null, int $page = null): LengthAwarePagina Assert::integer($this->memory, 'Memory usage must be an int, got %s'); $query = Node::query()->select('nodes.*') - ->selectRaw('IFNULL(SUM(servers.memory), 0) as sum_memory') - ->selectRaw('IFNULL(SUM(servers.disk), 0) as sum_disk') + ->selectRaw('COALESCE(SUM(servers.memory), 0) as sum_memory') + ->selectRaw('COALESCE(SUM(servers.disk), 0) as sum_disk') ->leftJoin('servers', 'servers.node_id', '=', 'nodes.id') ->where('nodes.public', 1); @@ -82,8 +82,8 @@ public function handle(int $perPage = null, int $page = null): LengthAwarePagina } $results = $query->groupBy('nodes.id') - ->havingRaw('(IFNULL(SUM(servers.memory), 0) + ?) <= (nodes.memory * (1 + (nodes.memory_overallocate / 100)))', [$this->memory]) - ->havingRaw('(IFNULL(SUM(servers.disk), 0) + ?) <= (nodes.disk * (1 + (nodes.disk_overallocate / 100)))', [$this->disk]); + ->havingRaw('(COALESCE(SUM(servers.memory), 0) + ?) <= (nodes.memory * (1.0 + (nodes.memory_overallocate / 100.0)))', [$this->memory]) + ->havingRaw('(COALESCE(SUM(servers.disk), 0) + ?) <= (nodes.disk * (1.0 + (nodes.disk_overallocate / 100.0)))', [$this->disk]); if (!is_null($page)) { $results = $results->paginate($perPage ?? 50, ['*'], 'page', $page); diff --git a/bootstrap/tests.php b/bootstrap/tests.php index 5b54493554..66f23147d8 100644 --- a/bootstrap/tests.php +++ b/bootstrap/tests.php @@ -40,6 +40,8 @@ $output->writeln('Seeding database for Integration tests...' . PHP_EOL); $kernel->call('db:seed'); + + $output->writeln('Database configured, running Integration tests...' . PHP_EOL); } else { $output->writeln(PHP_EOL . 'Skipping database migrations...' . PHP_EOL); } diff --git a/config/database.php b/config/database.php index b3a460ba28..1a8d9bf650 100644 --- a/config/database.php +++ b/config/database.php @@ -57,6 +57,21 @@ PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT => env('MYSQL_ATTR_SSL_VERIFY_SERVER_CERT', true), ]) : [], ], + + 'pgsql' => [ + 'driver' => 'pgsql', + 'url' => env('DATABASE_URL'), + 'host' => env('DB_HOST', '127.0.0.1'), + 'port' => env('DB_PORT', '5432'), + 'database' => env('DB_DATABASE', 'panel'), + 'username' => env('DB_USERNAME', 'pterodactyl'), + 'password' => env('DB_PASSWORD', ''), + 'charset' => 'utf8', + 'prefix' => env('DB_PREFIX', ''), + 'prefix_indexes' => true, + 'search_path' => 'public', + 'sslmode' => 'prefer', + ], ], /* diff --git a/database/migrations/2016_01_23_195641_add_allocations_table.php b/database/migrations/2016_01_23_195641_add_allocations_table.php index cfff2b359e..e6306c3b29 100644 --- a/database/migrations/2016_01_23_195641_add_allocations_table.php +++ b/database/migrations/2016_01_23_195641_add_allocations_table.php @@ -1,5 +1,6 @@ increments('id'); @@ -23,7 +24,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::dropIfExists('allocations'); } diff --git a/database/migrations/2016_01_23_195851_add_api_keys.php b/database/migrations/2016_01_23_195851_add_api_keys.php index af7deb62df..1a7824b1c0 100644 --- a/database/migrations/2016_01_23_195851_add_api_keys.php +++ b/database/migrations/2016_01_23_195851_add_api_keys.php @@ -1,5 +1,6 @@ increments('id'); @@ -22,7 +23,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::dropIfExists('api_keys'); } diff --git a/database/migrations/2016_01_23_200044_add_api_permissions.php b/database/migrations/2016_01_23_200044_add_api_permissions.php index e6f6bcbf86..e587da0a3b 100644 --- a/database/migrations/2016_01_23_200044_add_api_permissions.php +++ b/database/migrations/2016_01_23_200044_add_api_permissions.php @@ -1,5 +1,6 @@ increments('id'); @@ -20,7 +21,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::dropIfExists('api_permissions'); } diff --git a/database/migrations/2016_01_23_200159_add_downloads.php b/database/migrations/2016_01_23_200159_add_downloads.php index b1771c5e46..9424578fb9 100644 --- a/database/migrations/2016_01_23_200159_add_downloads.php +++ b/database/migrations/2016_01_23_200159_add_downloads.php @@ -1,5 +1,6 @@ increments('id'); @@ -22,7 +23,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::dropIfExists('downloads'); } diff --git a/database/migrations/2016_01_23_200421_create_failed_jobs_table.php b/database/migrations/2016_01_23_200421_create_failed_jobs_table.php index 83923e7d03..50d42ccc97 100644 --- a/database/migrations/2016_01_23_200421_create_failed_jobs_table.php +++ b/database/migrations/2016_01_23_200421_create_failed_jobs_table.php @@ -1,5 +1,6 @@ increments('id'); @@ -22,7 +23,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::dropIfExists('failed_jobs'); } diff --git a/database/migrations/2016_01_23_200440_create_jobs_table.php b/database/migrations/2016_01_23_200440_create_jobs_table.php index 277acae31b..fe7f9686c0 100644 --- a/database/migrations/2016_01_23_200440_create_jobs_table.php +++ b/database/migrations/2016_01_23_200440_create_jobs_table.php @@ -1,5 +1,6 @@ bigIncrements('id'); @@ -19,6 +20,7 @@ public function up() $table->unsignedInteger('reserved_at')->nullable(); $table->unsignedInteger('available_at'); $table->unsignedInteger('created_at'); + $table->index(['queue', 'reserved', 'reserved_at']); }); } @@ -26,7 +28,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::dropIfExists('jobs'); } diff --git a/database/migrations/2016_01_23_200528_add_locations.php b/database/migrations/2016_01_23_200528_add_locations.php index b34a5fbcce..38d1e1710a 100644 --- a/database/migrations/2016_01_23_200528_add_locations.php +++ b/database/migrations/2016_01_23_200528_add_locations.php @@ -1,5 +1,6 @@ increments('id'); @@ -21,7 +22,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::dropIfExists('locations'); } diff --git a/database/migrations/2016_01_23_200648_add_nodes.php b/database/migrations/2016_01_23_200648_add_nodes.php index 52c0a29e68..371ebf0495 100644 --- a/database/migrations/2016_01_23_200648_add_nodes.php +++ b/database/migrations/2016_01_23_200648_add_nodes.php @@ -1,5 +1,6 @@ increments('id'); @@ -23,7 +24,7 @@ public function up() $table->mediumInteger('disk_overallocate')->unsigned()->nullable(); $table->char('daemonSecret', 36)->unique(); $table->smallInteger('daemonListen')->unsigned()->default(8080); - $table->smallInteger('daemonSFTP')->unsgined()->default(2022); + $table->smallInteger('daemonSFTP')->unsigned()->default(2022); $table->string('daemonBase')->default('/home/daemon-files'); $table->timestamps(); }); @@ -32,7 +33,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::dropIfExists('nodes'); } diff --git a/database/migrations/2016_01_23_201433_add_password_resets.php b/database/migrations/2016_01_23_201433_add_password_resets.php index 0584e36178..47c49146d8 100644 --- a/database/migrations/2016_01_23_201433_add_password_resets.php +++ b/database/migrations/2016_01_23_201433_add_password_resets.php @@ -1,5 +1,6 @@ string('email')->index(); @@ -20,7 +21,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::dropIfExists('password_resets'); } diff --git a/database/migrations/2016_01_23_201531_add_permissions.php b/database/migrations/2016_01_23_201531_add_permissions.php index 12c9bbe0fa..120a0e0340 100644 --- a/database/migrations/2016_01_23_201531_add_permissions.php +++ b/database/migrations/2016_01_23_201531_add_permissions.php @@ -1,5 +1,6 @@ increments('id'); @@ -22,7 +23,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::dropIfExists('permissions'); } diff --git a/database/migrations/2016_01_23_201649_add_server_variables.php b/database/migrations/2016_01_23_201649_add_server_variables.php index d9a436e6d0..596c619d0c 100644 --- a/database/migrations/2016_01_23_201649_add_server_variables.php +++ b/database/migrations/2016_01_23_201649_add_server_variables.php @@ -1,5 +1,6 @@ increments('id'); @@ -22,7 +23,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::dropIfExists('server_variables'); } diff --git a/database/migrations/2016_01_23_201748_add_servers.php b/database/migrations/2016_01_23_201748_add_servers.php index 5e10610691..901c1ff5c2 100644 --- a/database/migrations/2016_01_23_201748_add_servers.php +++ b/database/migrations/2016_01_23_201748_add_servers.php @@ -1,5 +1,6 @@ increments('id'); @@ -39,7 +40,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::dropIfExists('servers'); } diff --git a/database/migrations/2016_01_23_202544_add_service_options.php b/database/migrations/2016_01_23_202544_add_service_options.php index 7b0a336091..382f67a1a5 100644 --- a/database/migrations/2016_01_23_202544_add_service_options.php +++ b/database/migrations/2016_01_23_202544_add_service_options.php @@ -1,5 +1,6 @@ increments('id'); @@ -24,7 +25,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::dropIfExists('service_options'); } diff --git a/database/migrations/2016_01_23_202731_add_service_varibles.php b/database/migrations/2016_01_23_202731_add_service_varibles.php index e79fa1fe99..bb96d83b6a 100644 --- a/database/migrations/2016_01_23_202731_add_service_varibles.php +++ b/database/migrations/2016_01_23_202731_add_service_varibles.php @@ -1,5 +1,6 @@ increments('id'); @@ -28,7 +29,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::dropIfExists('service_variables'); } diff --git a/database/migrations/2016_01_23_202943_add_services.php b/database/migrations/2016_01_23_202943_add_services.php index 31f7234450..caddd964be 100644 --- a/database/migrations/2016_01_23_202943_add_services.php +++ b/database/migrations/2016_01_23_202943_add_services.php @@ -1,5 +1,6 @@ increments('id'); @@ -24,7 +25,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::dropIfExists('services'); } diff --git a/database/migrations/2016_01_23_203119_create_settings_table.php b/database/migrations/2016_01_23_203119_create_settings_table.php index 2cd6922c2b..40dec55c60 100644 --- a/database/migrations/2016_01_23_203119_create_settings_table.php +++ b/database/migrations/2016_01_23_203119_create_settings_table.php @@ -1,5 +1,6 @@ string('key')->unique(); @@ -19,7 +20,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::dropIfExists('settings'); } diff --git a/database/migrations/2016_01_23_203150_add_subusers.php b/database/migrations/2016_01_23_203150_add_subusers.php index 2f0e463100..e7561c0fd1 100644 --- a/database/migrations/2016_01_23_203150_add_subusers.php +++ b/database/migrations/2016_01_23_203150_add_subusers.php @@ -1,5 +1,6 @@ increments('id'); @@ -22,7 +23,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::dropIfExists('subusers'); } diff --git a/database/migrations/2016_01_23_203159_add_users.php b/database/migrations/2016_01_23_203159_add_users.php index 05ace7e22e..1b6f9b5bb5 100644 --- a/database/migrations/2016_01_23_203159_add_users.php +++ b/database/migrations/2016_01_23_203159_add_users.php @@ -1,5 +1,6 @@ increments('id'); @@ -27,7 +28,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::dropIfExists('users'); } diff --git a/database/migrations/2016_01_23_203947_create_sessions_table.php b/database/migrations/2016_01_23_203947_create_sessions_table.php index 533fa8aa26..7f708195ea 100644 --- a/database/migrations/2016_01_23_203947_create_sessions_table.php +++ b/database/migrations/2016_01_23_203947_create_sessions_table.php @@ -1,5 +1,6 @@ string('id')->unique(); @@ -23,7 +24,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::drop('sessions'); } diff --git a/database/migrations/2016_01_25_234418_rename_permissions_column.php b/database/migrations/2016_01_25_234418_rename_permissions_column.php index ae46dceb25..6b75986f93 100644 --- a/database/migrations/2016_01_25_234418_rename_permissions_column.php +++ b/database/migrations/2016_01_25_234418_rename_permissions_column.php @@ -1,5 +1,6 @@ renameColumn('permissions', 'permission'); @@ -18,7 +19,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('permissions', function (Blueprint $table) { }); diff --git a/database/migrations/2016_02_07_172148_add_databases_tables.php b/database/migrations/2016_02_07_172148_add_databases_tables.php index 7b1048b15e..26fdbf3892 100644 --- a/database/migrations/2016_02_07_172148_add_databases_tables.php +++ b/database/migrations/2016_02_07_172148_add_databases_tables.php @@ -1,5 +1,6 @@ increments('id'); @@ -25,7 +26,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::drop('databases'); } diff --git a/database/migrations/2016_02_07_181319_add_database_servers_table.php b/database/migrations/2016_02_07_181319_add_database_servers_table.php index 5a6740ae6e..16d2d3cf5a 100644 --- a/database/migrations/2016_02_07_181319_add_database_servers_table.php +++ b/database/migrations/2016_02_07_181319_add_database_servers_table.php @@ -1,5 +1,6 @@ increments('id'); @@ -26,7 +27,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::drop('database_servers'); } diff --git a/database/migrations/2016_02_13_154306_add_service_option_default_startup.php b/database/migrations/2016_02_13_154306_add_service_option_default_startup.php index c8255ff474..a5d14b6d55 100644 --- a/database/migrations/2016_02_13_154306_add_service_option_default_startup.php +++ b/database/migrations/2016_02_13_154306_add_service_option_default_startup.php @@ -1,5 +1,6 @@ text('executable')->after('docker_image')->nullable()->default(null); @@ -19,7 +20,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('service_options', function (Blueprint $table) { $table->dropColumn('executable'); diff --git a/database/migrations/2016_02_20_155318_add_unique_service_field.php b/database/migrations/2016_02_20_155318_add_unique_service_field.php index 01ff913598..241e278ffb 100644 --- a/database/migrations/2016_02_20_155318_add_unique_service_field.php +++ b/database/migrations/2016_02_20_155318_add_unique_service_field.php @@ -1,5 +1,6 @@ string('file')->unique()->change(); @@ -18,10 +19,10 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('services', function (Blueprint $table) { - $table->dropUnique('services_file_unique'); + $table->dropUnique(['file']); }); } } diff --git a/database/migrations/2016_02_27_163411_add_tasks_table.php b/database/migrations/2016_02_27_163411_add_tasks_table.php index f4cb7b1e36..8fb1efb4aa 100644 --- a/database/migrations/2016_02_27_163411_add_tasks_table.php +++ b/database/migrations/2016_02_27_163411_add_tasks_table.php @@ -1,5 +1,6 @@ increments('id'); @@ -32,7 +33,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::drop('tasks'); } diff --git a/database/migrations/2016_02_27_163447_add_tasks_log_table.php b/database/migrations/2016_02_27_163447_add_tasks_log_table.php index 265e7fd96b..6014a69b81 100644 --- a/database/migrations/2016_02_27_163447_add_tasks_log_table.php +++ b/database/migrations/2016_02_27_163447_add_tasks_log_table.php @@ -1,5 +1,6 @@ increments('id'); @@ -23,7 +24,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::drop('tasks_log'); } diff --git a/database/migrations/2016_03_18_155649_add_nullable_field_lastrun.php b/database/migrations/2016_03_18_155649_add_nullable_field_lastrun.php deleted file mode 100644 index 9d4752eb69..0000000000 --- a/database/migrations/2016_03_18_155649_add_nullable_field_lastrun.php +++ /dev/null @@ -1,24 +0,0 @@ -wrapTable('tasks'); - DB::statement('ALTER TABLE ' . $table . ' CHANGE `last_run` `last_run` TIMESTAMP NULL;'); - } - - /** - * Reverse the migrations. - */ - public function down() - { - $table = DB::getQueryGrammar()->wrapTable('tasks'); - DB::statement('ALTER TABLE ' . $table . ' CHANGE `last_run` `last_run` TIMESTAMP;'); - } -} diff --git a/database/migrations/2016_08_30_212718_add_ip_alias.php b/database/migrations/2016_08_30_212718_add_ip_alias.php index 26aa5eaa53..17272a2cc6 100644 --- a/database/migrations/2016_08_30_212718_add_ip_alias.php +++ b/database/migrations/2016_08_30_212718_add_ip_alias.php @@ -1,5 +1,6 @@ text('ip_alias')->nullable()->after('ip'); @@ -29,7 +30,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('allocations', function (Blueprint $table) { $table->dropColumn('ip_alias'); diff --git a/database/migrations/2016_08_30_213301_modify_ip_storage_method.php b/database/migrations/2016_08_30_213301_modify_ip_storage_method.php index ee7e704fb0..7c8b1d46bf 100644 --- a/database/migrations/2016_08_30_213301_modify_ip_storage_method.php +++ b/database/migrations/2016_08_30_213301_modify_ip_storage_method.php @@ -1,5 +1,6 @@ mediumInteger('allocation')->unsigned()->after('oom_disabled'); @@ -47,7 +48,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('servers', function (Blueprint $table) { $table->text('ip')->after('allocation'); diff --git a/database/migrations/2016_09_01_193520_add_suspension_for_servers.php b/database/migrations/2016_09_01_193520_add_suspension_for_servers.php index 7bfb75b200..19cd965229 100644 --- a/database/migrations/2016_09_01_193520_add_suspension_for_servers.php +++ b/database/migrations/2016_09_01_193520_add_suspension_for_servers.php @@ -1,5 +1,6 @@ tinyInteger('suspended')->unsigned()->default(0)->after('active'); @@ -18,7 +19,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('servers', function (Blueprint $table) { $table->dropColumn('suspended'); diff --git a/database/migrations/2016_09_01_211924_remove_active_column.php b/database/migrations/2016_09_01_211924_remove_active_column.php index 22a2bde136..7450c932d0 100644 --- a/database/migrations/2016_09_01_211924_remove_active_column.php +++ b/database/migrations/2016_09_01_211924_remove_active_column.php @@ -1,5 +1,6 @@ dropColumn('active'); @@ -18,7 +19,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('servers', function (Blueprint $table) { $table->tinyInteger('active')->after('name')->unsigned()->default(0); diff --git a/database/migrations/2016_09_02_190647_add_sftp_password_storage.php b/database/migrations/2016_09_02_190647_add_sftp_password_storage.php index 565957d599..57ce1f3b58 100644 --- a/database/migrations/2016_09_02_190647_add_sftp_password_storage.php +++ b/database/migrations/2016_09_02_190647_add_sftp_password_storage.php @@ -1,5 +1,6 @@ text('sftp_password')->after('username')->nullable(); @@ -18,7 +19,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('servers', function (Blueprint $table) { $table->dropColumn('sftp_password'); diff --git a/database/migrations/2016_09_04_171338_update_jobs_tables.php b/database/migrations/2016_09_04_171338_update_jobs_tables.php index 840ecacb58..4c5bff23fd 100644 --- a/database/migrations/2016_09_04_171338_update_jobs_tables.php +++ b/database/migrations/2016_09_04_171338_update_jobs_tables.php @@ -9,11 +9,12 @@ class UpdateJobsTables extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('jobs', function (Blueprint $table) { - $table->dropIndex('jobs_queue_reserved_reserved_at_index'); + $table->dropIndex(['queue', 'reserved', 'reserved_at']); $table->dropColumn('reserved'); + $table->index(['queue', 'reserved_at']); }); } @@ -21,10 +22,11 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('jobs', function (Blueprint $table) { - $table->dropIndex('jobs_queue_reserved_at_index'); + $table->dropIndex(['queue', 'reserved_at']); + $table->tinyInteger('reserved')->unsigned(); $table->index(['queue', 'reserved', 'reserved_at']); }); diff --git a/database/migrations/2016_09_04_172028_update_failed_jobs_table.php b/database/migrations/2016_09_04_172028_update_failed_jobs_table.php index a00f5f18d7..b5157a1eff 100644 --- a/database/migrations/2016_09_04_172028_update_failed_jobs_table.php +++ b/database/migrations/2016_09_04_172028_update_failed_jobs_table.php @@ -9,7 +9,7 @@ class UpdateFailedJobsTable extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('failed_jobs', function (Blueprint $table) { $table->text('exception'); @@ -19,7 +19,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('failed_jobs', function (Blueprint $table) { $table->dropColumn('exception'); diff --git a/database/migrations/2016_09_04_182835_create_notifications_table.php b/database/migrations/2016_09_04_182835_create_notifications_table.php index 30fc23a59a..8918f30090 100644 --- a/database/migrations/2016_09_04_182835_create_notifications_table.php +++ b/database/migrations/2016_09_04_182835_create_notifications_table.php @@ -1,5 +1,6 @@ string('id')->primary(); @@ -23,7 +24,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::drop('notifications'); } diff --git a/database/migrations/2016_09_07_163017_add_unique_identifier.php b/database/migrations/2016_09_07_163017_add_unique_identifier.php index e1bab9ccc0..685a718a46 100644 --- a/database/migrations/2016_09_07_163017_add_unique_identifier.php +++ b/database/migrations/2016_09_07_163017_add_unique_identifier.php @@ -9,7 +9,7 @@ class AddUniqueIdentifier extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('services', function (Blueprint $table) { $table->char('author', 36)->after('id'); @@ -19,7 +19,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('services', function (Blueprint $table) { $table->dropColumn('author'); diff --git a/database/migrations/2016_09_14_145945_allow_longer_regex_field.php b/database/migrations/2016_09_14_145945_allow_longer_regex_field.php index a7df1ca1b3..8d0ab04ac4 100644 --- a/database/migrations/2016_09_14_145945_allow_longer_regex_field.php +++ b/database/migrations/2016_09_14_145945_allow_longer_regex_field.php @@ -9,7 +9,7 @@ class AllowLongerRegexField extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('service_variables', function (Blueprint $table) { $table->text('regex')->change(); @@ -19,7 +19,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('service_variables', function (Blueprint $table) { $table->string('regex')->change(); diff --git a/database/migrations/2016_09_17_194246_add_docker_image_column.php b/database/migrations/2016_09_17_194246_add_docker_image_column.php index 05d26112ec..0e66f649e3 100644 --- a/database/migrations/2016_09_17_194246_add_docker_image_column.php +++ b/database/migrations/2016_09_17_194246_add_docker_image_column.php @@ -1,5 +1,6 @@ string('image')->after('daemonSecret'); @@ -32,7 +33,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('servers', function (Blueprint $table) { $table->dropColumn('image'); diff --git a/database/migrations/2016_09_21_165554_update_servers_column_name.php b/database/migrations/2016_09_21_165554_update_servers_column_name.php index 14ae07c4a5..919cdcaab7 100644 --- a/database/migrations/2016_09_21_165554_update_servers_column_name.php +++ b/database/migrations/2016_09_21_165554_update_servers_column_name.php @@ -9,7 +9,7 @@ class UpdateServersColumnName extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('databases', function (Blueprint $table) { $table->renameColumn('server', 'server_id'); @@ -19,7 +19,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('databases', function (Blueprint $table) { $table->renameColumn('server_id', 'server'); diff --git a/database/migrations/2016_09_29_213518_rename_double_insurgency.php b/database/migrations/2016_09_29_213518_rename_double_insurgency.php index adb577754c..5f21c70365 100644 --- a/database/migrations/2016_09_29_213518_rename_double_insurgency.php +++ b/database/migrations/2016_09_29_213518_rename_double_insurgency.php @@ -7,7 +7,7 @@ class RenameDoubleInsurgency extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { DB::transaction(function () { $model = DB::table('service_options')->where('parent_service', 2)->where('id', 3)->where('name', 'Insurgency')->first(); @@ -21,7 +21,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { } } diff --git a/database/migrations/2016_10_07_152117_build_api_log_table.php b/database/migrations/2016_10_07_152117_build_api_log_table.php index 08ea312dcc..39356c5bab 100644 --- a/database/migrations/2016_10_07_152117_build_api_log_table.php +++ b/database/migrations/2016_10_07_152117_build_api_log_table.php @@ -9,7 +9,7 @@ class BuildApiLogTable extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::create('api_logs', function (Blueprint $table) { $table->increments('id'); @@ -28,7 +28,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::drop('api_logs'); } diff --git a/database/migrations/2016_10_14_164802_update_api_keys.php b/database/migrations/2016_10_14_164802_update_api_keys.php index 56c3e80977..b43eef1b1e 100644 --- a/database/migrations/2016_10_14_164802_update_api_keys.php +++ b/database/migrations/2016_10_14_164802_update_api_keys.php @@ -9,7 +9,7 @@ class UpdateApiKeys extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('api_keys', function (Blueprint $table) { $table->unsignedInteger('user')->after('id'); @@ -21,7 +21,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('api_keys', function (Blueprint $table) { $table->dropColumn('user'); diff --git a/database/migrations/2016_10_23_181719_update_misnamed_bungee.php b/database/migrations/2016_10_23_181719_update_misnamed_bungee.php index 70ec18b33e..a9cf3a35cd 100644 --- a/database/migrations/2016_10_23_181719_update_misnamed_bungee.php +++ b/database/migrations/2016_10_23_181719_update_misnamed_bungee.php @@ -7,7 +7,7 @@ class UpdateMisnamedBungee extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { DB::table('service_variables')->select('env_variable')->where('env_variable', 'BUNGE_VERSION')->update([ 'env_variable' => 'BUNGEE_VERSION', @@ -17,7 +17,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { } } diff --git a/database/migrations/2016_10_23_193810_add_foreign_keys_servers.php b/database/migrations/2016_10_23_193810_add_foreign_keys_servers.php index 1412720c9a..da0fc7c834 100644 --- a/database/migrations/2016_10_23_193810_add_foreign_keys_servers.php +++ b/database/migrations/2016_10_23_193810_add_foreign_keys_servers.php @@ -9,22 +9,21 @@ class AddForeignKeysServers extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { - DB::statement('ALTER TABLE servers - MODIFY COLUMN node INT(10) UNSIGNED NOT NULL, - MODIFY COLUMN owner INT(10) UNSIGNED NOT NULL, - MODIFY COLUMN allocation INT(10) UNSIGNED NOT NULL, - MODIFY COLUMN service INT(10) UNSIGNED NOT NULL, - MODIFY COLUMN `option` INT(10) UNSIGNED NOT NULL - '); - Schema::table('servers', function (Blueprint $table) { + $table->integer('node', false, true)->change(); + $table->integer('owner', false, true)->change(); + $table->integer('allocation', false, true)->change(); + $table->integer('service', false, true)->change(); + $table->integer('option', false, true)->change(); + $table->foreign('node')->references('id')->on('nodes'); $table->foreign('owner')->references('id')->on('users'); $table->foreign('allocation')->references('id')->on('allocations'); $table->foreign('service')->references('id')->on('services'); $table->foreign('option')->references('id')->on('service_options'); + $table->softDeletes(); }); } @@ -32,30 +31,31 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('servers', function (Blueprint $table) { - $table->dropForeign('servers_node_foreign'); - $table->dropForeign('servers_owner_foreign'); - $table->dropForeign('servers_allocation_foreign'); - $table->dropForeign('servers_service_foreign'); - $table->dropForeign('servers_option_foreign'); - - $table->dropIndex('servers_node_foreign'); - $table->dropIndex('servers_owner_foreign'); - $table->dropIndex('servers_allocation_foreign'); - $table->dropIndex('servers_service_foreign'); - $table->dropIndex('servers_option_foreign'); + $table->dropForeign(['node']); + $table->dropIndex(['node']); + + $table->dropForeign(['owner']); + $table->dropIndex(['owner']); + + $table->dropForeign(['allocation']); + $table->dropIndex(['allocation']); + + $table->dropForeign(['service']); + $table->dropIndex(['service']); + + $table->dropForeign(['option']); + $table->dropIndex(['option']); $table->dropColumn('deleted_at'); - }); - DB::statement('ALTER TABLE servers - MODIFY COLUMN node MEDIUMINT(8) UNSIGNED NOT NULL, - MODIFY COLUMN owner MEDIUMINT(8) UNSIGNED NOT NULL, - MODIFY COLUMN allocation MEDIUMINT(8) UNSIGNED NOT NULL, - MODIFY COLUMN service MEDIUMINT(8) UNSIGNED NOT NULL, - MODIFY COLUMN `option` MEDIUMINT(8) UNSIGNED NOT NULL - '); + $table->mediumInteger('node', false, true)->change(); + $table->mediumInteger('owner', false, true)->change(); + $table->mediumInteger('allocation', false, true)->change(); + $table->mediumInteger('service', false, true)->change(); + $table->mediumInteger('option', false, true)->change(); + }); } } diff --git a/database/migrations/2016_10_23_201624_add_foreign_allocations.php b/database/migrations/2016_10_23_201624_add_foreign_allocations.php index 0660081cb7..7ae4b040dd 100644 --- a/database/migrations/2016_10_23_201624_add_foreign_allocations.php +++ b/database/migrations/2016_10_23_201624_add_foreign_allocations.php @@ -1,5 +1,6 @@ integer('assigned_to', false, true)->nullable()->change(); + $table->integer('node', false, true)->nullable(false)->change(); $table->foreign('assigned_to')->references('id')->on('servers'); $table->foreign('node')->references('id')->on('nodes'); }); @@ -25,14 +23,17 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('allocations', function (Blueprint $table) { - $table->dropForeign('allocations_assigned_to_foreign'); - $table->dropForeign('allocations_node_foreign'); + $table->dropForeign(['assigned_to']); + $table->dropIndex(['assigned_to']); + + $table->dropForeign(['node']); + $table->dropIndex(['node']); - $table->dropIndex('allocations_assigned_to_foreign'); - $table->dropIndex('allocations_node_foreign'); + $table->mediumInteger('assigned_to', false, true)->nullable()->change(); + $table->mediumInteger('node', false, true)->nullable(false)->change(); }); DB::statement('ALTER TABLE allocations diff --git a/database/migrations/2016_10_23_202222_add_foreign_api_keys.php b/database/migrations/2016_10_23_202222_add_foreign_api_keys.php index 700342d749..44b11d0e5a 100644 --- a/database/migrations/2016_10_23_202222_add_foreign_api_keys.php +++ b/database/migrations/2016_10_23_202222_add_foreign_api_keys.php @@ -9,7 +9,7 @@ class AddForeignApiKeys extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('api_keys', function (Blueprint $table) { $table->foreign('user')->references('id')->on('users'); @@ -19,11 +19,11 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('api_keys', function (Blueprint $table) { - $table->dropForeign('api_keys_user_foreign'); - $table->dropIndex('api_keys_user_foreign'); + $table->dropForeign(['user']); + $table->dropIndex(['user']); }); } } diff --git a/database/migrations/2016_10_23_202703_add_foreign_api_permissions.php b/database/migrations/2016_10_23_202703_add_foreign_api_permissions.php index d8eb3504da..2494eaba7d 100644 --- a/database/migrations/2016_10_23_202703_add_foreign_api_permissions.php +++ b/database/migrations/2016_10_23_202703_add_foreign_api_permissions.php @@ -9,11 +9,10 @@ class AddForeignApiPermissions extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { - DB::statement('ALTER TABLE api_permissions MODIFY key_id INT(10) UNSIGNED NOT NULL'); - Schema::table('api_permissions', function (Blueprint $table) { + $table->integer('key_id', false, true)->nullable(false)->change(); $table->foreign('key_id')->references('id')->on('api_keys'); }); } @@ -21,13 +20,13 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('api_permissions', function (Blueprint $table) { - $table->dropForeign('api_permissions_key_id_foreign'); - $table->dropIndex('api_permissions_key_id_foreign'); - }); + $table->dropForeign(['key_id']); + $table->dropIndex(['key_id']); - DB::statement('ALTER TABLE api_permissions MODIFY key_id MEDIUMINT(8) UNSIGNED NOT NULL'); + $table->mediumInteger('key_id', false, true)->nullable(false)->change(); + }); } } diff --git a/database/migrations/2016_10_23_202953_add_foreign_database_servers.php b/database/migrations/2016_10_23_202953_add_foreign_database_servers.php index 769b7daa3d..78ee8264db 100644 --- a/database/migrations/2016_10_23_202953_add_foreign_database_servers.php +++ b/database/migrations/2016_10_23_202953_add_foreign_database_servers.php @@ -9,7 +9,7 @@ class AddForeignDatabaseServers extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('database_servers', function (Blueprint $table) { $table->foreign('linked_node')->references('id')->on('nodes'); @@ -19,11 +19,11 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('database_servers', function (Blueprint $table) { - $table->dropForeign('database_servers_linked_node_foreign'); - $table->dropIndex('database_servers_linked_node_foreign'); + $table->dropForeign(['linked_node']); + $table->dropIndex(['linked_node']); }); } } diff --git a/database/migrations/2016_10_23_203105_add_foreign_databases.php b/database/migrations/2016_10_23_203105_add_foreign_databases.php index be26e3cb01..bea43049bf 100644 --- a/database/migrations/2016_10_23_203105_add_foreign_databases.php +++ b/database/migrations/2016_10_23_203105_add_foreign_databases.php @@ -9,7 +9,7 @@ class AddForeignDatabases extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('databases', function (Blueprint $table) { $table->foreign('server_id')->references('id')->on('servers'); @@ -20,14 +20,14 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('databases', function (Blueprint $table) { - $table->dropForeign('databases_server_id_foreign'); - $table->dropForeign('databases_db_server_foreign'); + $table->dropForeign(['server_id']); + $table->dropIndex(['server_id']); - $table->dropIndex('databases_server_id_foreign'); - $table->dropIndex('databases_db_server_foreign'); + $table->dropForeign(['db_server']); + $table->dropIndex(['db_server']); }); } } diff --git a/database/migrations/2016_10_23_203335_add_foreign_nodes.php b/database/migrations/2016_10_23_203335_add_foreign_nodes.php index f861e0a7d2..375189a7fd 100644 --- a/database/migrations/2016_10_23_203335_add_foreign_nodes.php +++ b/database/migrations/2016_10_23_203335_add_foreign_nodes.php @@ -9,11 +9,10 @@ class AddForeignNodes extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { - DB::statement('ALTER TABLE nodes MODIFY location INT(10) UNSIGNED NOT NULL'); - Schema::table('nodes', function (Blueprint $table) { + $table->integer('location', false, true)->nullable(false)->change(); $table->foreign('location')->references('id')->on('locations'); }); } @@ -21,13 +20,13 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('nodes', function (Blueprint $table) { - $table->dropForeign('nodes_location_foreign'); - $table->dropIndex('nodes_location_foreign'); - }); + $table->dropForeign(['location']); + $table->dropIndex(['location']); - DB::statement('ALTER TABLE nodes MODIFY location MEDIUMINT(10) UNSIGNED NOT NULL'); + $table->mediumInteger('location', false, true)->nullable(false)->change(); + }); } } diff --git a/database/migrations/2016_10_23_203522_add_foreign_permissions.php b/database/migrations/2016_10_23_203522_add_foreign_permissions.php index a43f0eacf3..78bbf32a5e 100644 --- a/database/migrations/2016_10_23_203522_add_foreign_permissions.php +++ b/database/migrations/2016_10_23_203522_add_foreign_permissions.php @@ -9,7 +9,7 @@ class AddForeignPermissions extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('permissions', function (Blueprint $table) { $table->foreign('user_id')->references('id')->on('users'); @@ -20,14 +20,14 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('permissions', function (Blueprint $table) { - $table->dropForeign('permissions_user_id_foreign'); - $table->dropForeign('permissions_server_id_foreign'); + $table->dropForeign(['user_id']); + $table->dropIndex(['user_id']); - $table->dropIndex('permissions_user_id_foreign'); - $table->dropIndex('permissions_server_id_foreign'); + $table->dropForeign(['server_id']); + $table->dropIndex(['server_id']); }); } } diff --git a/database/migrations/2016_10_23_203857_add_foreign_server_variables.php b/database/migrations/2016_10_23_203857_add_foreign_server_variables.php index b4720495d4..3ccc3d1835 100644 --- a/database/migrations/2016_10_23_203857_add_foreign_server_variables.php +++ b/database/migrations/2016_10_23_203857_add_foreign_server_variables.php @@ -9,14 +9,11 @@ class AddForeignServerVariables extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { - DB::statement('ALTER TABLE server_variables - MODIFY COLUMN server_id INT(10) UNSIGNED NULL, - MODIFY COLUMN variable_id INT(10) UNSIGNED NOT NULL - '); - Schema::table('server_variables', function (Blueprint $table) { + $table->integer('server_id', false, true)->nullable()->change(); + $table->integer('variable_id', false, true)->nullable(false)->change(); $table->foreign('server_id')->references('id')->on('servers'); $table->foreign('variable_id')->references('id')->on('service_variables'); }); @@ -25,16 +22,13 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('server_variables', function (Blueprint $table) { $table->dropForeign(['server_id']); $table->dropForeign(['variable_id']); + $table->mediumInteger('server_id', false, true)->nullable()->change(); + $table->mediumInteger('variable_id', false, true)->nullable(false)->change(); }); - - DB::statement('ALTER TABLE server_variables - MODIFY COLUMN server_id MEDIUMINT(8) UNSIGNED NULL, - MODIFY COLUMN variable_id MEDIUMINT(8) UNSIGNED NOT NULL - '); } } diff --git a/database/migrations/2016_10_23_204157_add_foreign_service_options.php b/database/migrations/2016_10_23_204157_add_foreign_service_options.php index cb8c0e2e85..9f01905b7f 100644 --- a/database/migrations/2016_10_23_204157_add_foreign_service_options.php +++ b/database/migrations/2016_10_23_204157_add_foreign_service_options.php @@ -9,11 +9,10 @@ class AddForeignServiceOptions extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { - DB::statement('ALTER TABLE service_options MODIFY parent_service INT(10) UNSIGNED NOT NULL'); - Schema::table('service_options', function (Blueprint $table) { + $table->integer('parent_service', false, true)->change(); $table->foreign('parent_service')->references('id')->on('services'); }); } @@ -21,13 +20,13 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('service_options', function (Blueprint $table) { - $table->dropForeign('service_options_parent_service_foreign'); - $table->dropIndex('service_options_parent_service_foreign'); - }); + $table->dropForeign(['parent_service']); + $table->dropIndex(['parent_service']); - DB::statement('ALTER TABLE service_options MODIFY parent_service MEDIUMINT(8) UNSIGNED NOT NULL'); + $table->mediumInteger('parent_service', false, true)->change(); + }); } } diff --git a/database/migrations/2016_10_23_204321_add_foreign_service_variables.php b/database/migrations/2016_10_23_204321_add_foreign_service_variables.php index 02bbc46f28..df998efafa 100644 --- a/database/migrations/2016_10_23_204321_add_foreign_service_variables.php +++ b/database/migrations/2016_10_23_204321_add_foreign_service_variables.php @@ -9,11 +9,10 @@ class AddForeignServiceVariables extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { - DB::statement('ALTER TABLE service_variables MODIFY option_id INT(10) UNSIGNED NOT NULL'); - Schema::table('service_variables', function (Blueprint $table) { + $table->integer('option_id', false, true)->change(); $table->foreign('option_id')->references('id')->on('service_options'); }); } @@ -21,13 +20,13 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('service_variables', function (Blueprint $table) { - $table->dropForeign('service_variables_option_id_foreign'); - $table->dropIndex('service_variables_option_id_foreign'); - }); + $table->dropForeign(['option_id']); + $table->dropIndex(['option_id']); - DB::statement('ALTER TABLE service_variables MODIFY option_id MEDIUMINT(8) UNSIGNED NOT NULL'); + $table->mediumInteger('option_id', false, true)->change(); + }); } } diff --git a/database/migrations/2016_10_23_204454_add_foreign_subusers.php b/database/migrations/2016_10_23_204454_add_foreign_subusers.php index b637c80ae5..ff4bb95a3e 100644 --- a/database/migrations/2016_10_23_204454_add_foreign_subusers.php +++ b/database/migrations/2016_10_23_204454_add_foreign_subusers.php @@ -9,7 +9,7 @@ class AddForeignSubusers extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('subusers', function (Blueprint $table) { $table->foreign('user_id')->references('id')->on('users'); @@ -20,14 +20,14 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('subusers', function (Blueprint $table) { - $table->dropForeign('subusers_user_id_foreign'); - $table->dropForeign('subusers_server_id_foreign'); + $table->dropForeign(['user_id']); + $table->dropIndex(['user_id']); - $table->dropIndex('subusers_user_id_foreign'); - $table->dropIndex('subusers_server_id_foreign'); + $table->dropForeign(['server_id']); + $table->dropIndex(['server_id']); }); } } diff --git a/database/migrations/2016_10_23_204610_add_foreign_tasks.php b/database/migrations/2016_10_23_204610_add_foreign_tasks.php index 18ea297e54..f32d89230e 100644 --- a/database/migrations/2016_10_23_204610_add_foreign_tasks.php +++ b/database/migrations/2016_10_23_204610_add_foreign_tasks.php @@ -9,7 +9,7 @@ class AddForeignTasks extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('tasks', function (Blueprint $table) { $table->foreign('server')->references('id')->on('servers'); @@ -19,7 +19,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('tasks', function (Blueprint $table) { $table->dropForeign(['server']); diff --git a/database/migrations/2016_11_04_000949_add_ark_service_option_fixed.php b/database/migrations/2016_11_04_000949_add_ark_service_option_fixed.php index 1547e32cc1..c5fff5523f 100644 --- a/database/migrations/2016_11_04_000949_add_ark_service_option_fixed.php +++ b/database/migrations/2016_11_04_000949_add_ark_service_option_fixed.php @@ -7,7 +7,7 @@ class AddArkServiceOptionFixed extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { DB::transaction(function () { $service = DB::table('services')->select('id')->where('author', 'ptrdctyl-v040-11e6-8b77-86f30ca893d3')->where('name', 'Source Engine')->first(); @@ -73,7 +73,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { DB::transaction(function () { $service = DB::table('services')->select('id')->where('author', 'ptrdctyl-v040-11e6-8b77-86f30ca893d3')->where('name', 'Source Engine')->first(); diff --git a/database/migrations/2016_11_11_220649_add_pack_support.php b/database/migrations/2016_11_11_220649_add_pack_support.php index b6fa0972ba..8fd638ae6c 100644 --- a/database/migrations/2016_11_11_220649_add_pack_support.php +++ b/database/migrations/2016_11_11_220649_add_pack_support.php @@ -9,7 +9,7 @@ class AddPackSupport extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::create('service_packs', function (Blueprint $table) { $table->increments('id'); @@ -29,7 +29,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::drop('service_packs'); } diff --git a/database/migrations/2016_11_11_231731_set_service_name_unique.php b/database/migrations/2016_11_11_231731_set_service_name_unique.php index 42b0f6953e..261fdb3563 100644 --- a/database/migrations/2016_11_11_231731_set_service_name_unique.php +++ b/database/migrations/2016_11_11_231731_set_service_name_unique.php @@ -9,7 +9,7 @@ class SetServiceNameUnique extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('services', function (Blueprint $table) { $table->unique('name'); @@ -19,7 +19,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('services', function (Blueprint $table) { $table->dropUnique('services_name_unique'); diff --git a/database/migrations/2016_11_27_142519_add_pack_column.php b/database/migrations/2016_11_27_142519_add_pack_column.php index d520466a88..3911ecb416 100644 --- a/database/migrations/2016_11_27_142519_add_pack_column.php +++ b/database/migrations/2016_11_27_142519_add_pack_column.php @@ -9,7 +9,7 @@ class AddPackColumn extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('servers', function (Blueprint $table) { $table->unsignedInteger('pack')->nullable()->after('option'); @@ -21,7 +21,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('servers', function (Blueprint $table) { $table->dropForeign(['pack']); diff --git a/database/migrations/2016_12_01_173018_add_configurable_upload_limit.php b/database/migrations/2016_12_01_173018_add_configurable_upload_limit.php index d2d14f4d00..c5136fe9e4 100644 --- a/database/migrations/2016_12_01_173018_add_configurable_upload_limit.php +++ b/database/migrations/2016_12_01_173018_add_configurable_upload_limit.php @@ -9,7 +9,7 @@ class AddConfigurableUploadLimit extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('nodes', function (Blueprint $table) { $table->unsignedInteger('upload_size')->after('disk_overallocate')->default(100); @@ -19,7 +19,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('nodes', function (Blueprint $table) { $table->dropColumn('upload_size'); diff --git a/database/migrations/2016_12_02_185206_correct_service_variables.php b/database/migrations/2016_12_02_185206_correct_service_variables.php index e9c87989ac..d94b3b78bd 100644 --- a/database/migrations/2016_12_02_185206_correct_service_variables.php +++ b/database/migrations/2016_12_02_185206_correct_service_variables.php @@ -7,7 +7,7 @@ class CorrectServiceVariables extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { DB::transaction(function () { // Modify Default Spigot Startup Line @@ -66,7 +66,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { // do nothing } diff --git a/database/migrations/2017_01_03_150436_fix_misnamed_option_tag.php b/database/migrations/2017_01_03_150436_fix_misnamed_option_tag.php index 7cdf96807c..35248d6bbd 100644 --- a/database/migrations/2017_01_03_150436_fix_misnamed_option_tag.php +++ b/database/migrations/2017_01_03_150436_fix_misnamed_option_tag.php @@ -7,7 +7,7 @@ class FixMisnamedOptionTag extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { DB::transaction(function () { DB::table('service_options')->where([ @@ -23,7 +23,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { DB::table('service_options')->where([ ['name', 'Sponge (SpongeVanilla)'], diff --git a/database/migrations/2017_01_07_154228_create_node_configuration_tokens_table.php b/database/migrations/2017_01_07_154228_create_node_configuration_tokens_table.php index 77693c265a..c4369f975b 100644 --- a/database/migrations/2017_01_07_154228_create_node_configuration_tokens_table.php +++ b/database/migrations/2017_01_07_154228_create_node_configuration_tokens_table.php @@ -9,7 +9,7 @@ class CreateNodeConfigurationTokensTable extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::create('node_configuration_tokens', function (Blueprint $table) { $table->increments('id'); @@ -24,7 +24,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::dropIfExists('node_configuration_tokens'); } diff --git a/database/migrations/2017_01_12_135449_add_more_user_data.php b/database/migrations/2017_01_12_135449_add_more_user_data.php index 0206040b50..82ae8c9e97 100644 --- a/database/migrations/2017_01_12_135449_add_more_user_data.php +++ b/database/migrations/2017_01_12_135449_add_more_user_data.php @@ -10,7 +10,7 @@ class AddMoreUserData extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('users', function (Blueprint $table) { $table->string('name_first')->after('email')->nullable(); @@ -34,7 +34,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('users', function (Blueprint $table) { $table->dropColumn('name_first'); diff --git a/database/migrations/2017_02_02_175548_UpdateColumnNames.php b/database/migrations/2017_02_02_175548_UpdateColumnNames.php index c88aa8de73..7195133130 100644 --- a/database/migrations/2017_02_02_175548_UpdateColumnNames.php +++ b/database/migrations/2017_02_02_175548_UpdateColumnNames.php @@ -9,22 +9,15 @@ class UpdateColumnNames extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('servers', function (Blueprint $table) { - $table->dropForeign('servers_node_foreign'); - $table->dropForeign('servers_owner_foreign'); - $table->dropForeign('servers_allocation_foreign'); - $table->dropForeign('servers_service_foreign'); - $table->dropForeign('servers_option_foreign'); - $table->dropForeign('servers_pack_foreign'); - - $table->dropIndex('servers_node_foreign'); - $table->dropIndex('servers_owner_foreign'); - $table->dropIndex('servers_allocation_foreign'); - $table->dropIndex('servers_service_foreign'); - $table->dropIndex('servers_option_foreign'); - $table->dropIndex('servers_pack_foreign'); + $table->dropForeign(['node']); + $table->dropForeign(['owner']); + $table->dropForeign(['allocation']); + $table->dropForeign(['service']); + $table->dropForeign(['option']); + $table->dropForeign(['pack']); $table->renameColumn('node', 'node_id'); $table->renameColumn('owner', 'owner_id'); @@ -47,14 +40,10 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('servers', function (Blueprint $table) { - $table->dropForeign(['node_id']); - $table->dropForeign(['owner_id']); - $table->dropForeign(['allocation_id']); - $table->dropForeign(['service_id']); - $table->dropForeign(['option_id']); + $table->dropForeign(['node_id', 'owner_id', 'allocation_id', 'service_id', 'option_id']); $table->renameColumn('node_id', 'node'); $table->renameColumn('owner_id', 'owner'); diff --git a/database/migrations/2017_02_03_140948_UpdateNodesTable.php b/database/migrations/2017_02_03_140948_UpdateNodesTable.php index 58ec63ef4a..e797cc704f 100644 --- a/database/migrations/2017_02_03_140948_UpdateNodesTable.php +++ b/database/migrations/2017_02_03_140948_UpdateNodesTable.php @@ -9,11 +9,10 @@ class UpdateNodesTable extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('nodes', function (Blueprint $table) { - $table->dropForeign('nodes_location_foreign'); - $table->dropIndex('nodes_location_foreign'); + $table->dropForeign(['location']); $table->renameColumn('location', 'location_id'); $table->foreign('location_id')->references('id')->on('locations'); @@ -23,11 +22,10 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('nodes', function (Blueprint $table) { - $table->dropForeign('nodes_location_id_foreign'); - $table->dropIndex('nodes_location_id_foreign'); + $table->dropForeign(['location_id']); $table->renameColumn('location_id', 'location'); $table->foreign('location')->references('id')->on('locations'); diff --git a/database/migrations/2017_02_03_155554_RenameColumns.php b/database/migrations/2017_02_03_155554_RenameColumns.php index 5f617abec1..bd50e16bec 100644 --- a/database/migrations/2017_02_03_155554_RenameColumns.php +++ b/database/migrations/2017_02_03_155554_RenameColumns.php @@ -9,13 +9,11 @@ class RenameColumns extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('allocations', function (Blueprint $table) { - $table->dropForeign('allocations_node_foreign'); - $table->dropForeign('allocations_assigned_to_foreign'); - $table->dropIndex('allocations_node_foreign'); - $table->dropIndex('allocations_assigned_to_foreign'); + $table->dropForeign(['node']); + $table->dropForeign(['assigned_to']); $table->renameColumn('node', 'node_id'); $table->renameColumn('assigned_to', 'server_id'); @@ -27,13 +25,13 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('allocations', function (Blueprint $table) { - $table->dropForeign('allocations_node_id_foreign'); - $table->dropForeign('allocations_server_id_foreign'); - $table->dropIndex('allocations_node_id_foreign'); - $table->dropIndex('allocations_server_id_foreign'); + $table->dropForeign(['node_id']); + $table->dropForeign(['server_id']); + $table->dropIndex(['node_id']); + $table->dropIndex(['server_id']); $table->renameColumn('node_id', 'node'); $table->renameColumn('server_id', 'assigned_to'); diff --git a/database/migrations/2017_02_05_164123_AdjustColumnNames.php b/database/migrations/2017_02_05_164123_AdjustColumnNames.php index c7688f0564..51c8818c7b 100644 --- a/database/migrations/2017_02_05_164123_AdjustColumnNames.php +++ b/database/migrations/2017_02_05_164123_AdjustColumnNames.php @@ -9,11 +9,10 @@ class AdjustColumnNames extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('service_options', function (Blueprint $table) { - $table->dropForeign('service_options_parent_service_foreign'); - $table->dropIndex('service_options_parent_service_foreign'); + $table->dropForeign(['parent_service']); $table->renameColumn('parent_service', 'service_id'); $table->foreign('service_id')->references('id')->on('services'); @@ -23,11 +22,11 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('service_options', function (Blueprint $table) { - $table->dropForeign('service_options_service_id_foreign'); - $table->dropIndex('service_options_service_id_foreign'); + $table->dropForeign(['service_id']); + $table->dropIndex(['service_id']); $table->renameColumn('service_id', 'parent_service'); $table->foreign('parent_service')->references('id')->on('services'); diff --git a/database/migrations/2017_02_05_164516_AdjustColumnNamesForServicePacks.php b/database/migrations/2017_02_05_164516_AdjustColumnNamesForServicePacks.php index 6f86b3b6e7..69dc33dda2 100644 --- a/database/migrations/2017_02_05_164516_AdjustColumnNamesForServicePacks.php +++ b/database/migrations/2017_02_05_164516_AdjustColumnNamesForServicePacks.php @@ -9,11 +9,10 @@ class AdjustColumnNamesForServicePacks extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('service_packs', function (Blueprint $table) { - $table->dropForeign('service_packs_option_foreign'); - $table->dropIndex('service_packs_option_foreign'); + $table->dropForeign(['option']); $table->renameColumn('option', 'option_id'); $table->foreign('option_id')->references('id')->on('service_options'); @@ -23,11 +22,11 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('service_packs', function (Blueprint $table) { - $table->dropForeign('service_packs_option_id_foreign'); - $table->dropIndex('service_packs_option_id_foreign'); + $table->dropForeign(['option_id']); + $table->dropIndex(['option_id']); $table->renameColumn('option_id', 'option'); $table->foreign('option')->references('id')->on('service_options'); diff --git a/database/migrations/2017_02_09_174834_SetupPermissionsPivotTable.php b/database/migrations/2017_02_09_174834_SetupPermissionsPivotTable.php index 45efce83a8..bf6469506b 100644 --- a/database/migrations/2017_02_09_174834_SetupPermissionsPivotTable.php +++ b/database/migrations/2017_02_09_174834_SetupPermissionsPivotTable.php @@ -11,7 +11,7 @@ class SetupPermissionsPivotTable extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('permissions', function (Blueprint $table) { $table->unsignedInteger('subuser_id')->after('id'); @@ -19,17 +19,15 @@ public function up() DB::transaction(function () { foreach (Subuser::all() as &$subuser) { - Permission::where('user_id', $subuser->user_id)->where('server_id', $subuser->server_id)->update([ + Permission::query()->where('user_id', $subuser->user_id)->where('server_id', $subuser->server_id)->update([ 'subuser_id' => $subuser->id, ]); } }); Schema::table('permissions', function (Blueprint $table) { - $table->dropForeign('permissions_server_id_foreign'); - $table->dropIndex('permissions_server_id_foreign'); - $table->dropForeign('permissions_user_id_foreign'); - $table->dropIndex('permissions_user_id_foreign'); + $table->dropForeign(['server_id']); + $table->dropForeign(['user_id']); $table->dropColumn('server_id'); $table->dropColumn('user_id'); @@ -42,7 +40,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('permissions', function (Blueprint $table) { $table->unsignedInteger('server_id')->after('subuser_id'); @@ -52,7 +50,7 @@ public function down() DB::transaction(function () { foreach (Subuser::all() as &$subuser) { - Permission::where('subuser_id', $subuser->id)->update([ + Permission::query()->where('subuser_id', $subuser->id)->update([ 'user_id' => $subuser->user_id, 'server_id' => $subuser->server_id, ]); @@ -60,8 +58,8 @@ public function down() }); Schema::table('permissions', function (Blueprint $table) { - $table->dropForeign('permissions_subuser_id_foreign'); - $table->dropIndex('permissions_subuser_id_foreign'); + $table->dropForeign(['subuser_id']); + $table->dropIndex(['subuser_id']); $table->dropColumn('subuser_id'); $table->foreign('server_id')->references('id')->on('servers'); diff --git a/database/migrations/2017_02_10_171858_UpdateAPIKeyColumnNames.php b/database/migrations/2017_02_10_171858_UpdateAPIKeyColumnNames.php index 8b541d9415..8ae28c2c95 100644 --- a/database/migrations/2017_02_10_171858_UpdateAPIKeyColumnNames.php +++ b/database/migrations/2017_02_10_171858_UpdateAPIKeyColumnNames.php @@ -9,10 +9,10 @@ class UpdateAPIKeyColumnNames extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('api_keys', function (Blueprint $table) { - $table->dropForeign('api_keys_user_foreign')->dropIndex('api_keys_user_foreign'); + $table->dropForeign(['user']); $table->renameColumn('user', 'user_id'); $table->foreign('user_id')->references('id')->on('users'); @@ -22,10 +22,10 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('api_keys', function (Blueprint $table) { - $table->dropForeign('api_keys_user_id_foreign')->dropIndex('api_keys_user_id_foreign'); + $table->dropForeign(['user_id']); $table->renameColumn('user_id', 'user'); $table->foreign('user')->references('id')->on('users'); diff --git a/database/migrations/2017_03_03_224254_UpdateNodeConfigTokensColumns.php b/database/migrations/2017_03_03_224254_UpdateNodeConfigTokensColumns.php index 4f27346fa4..aab6c2b95a 100644 --- a/database/migrations/2017_03_03_224254_UpdateNodeConfigTokensColumns.php +++ b/database/migrations/2017_03_03_224254_UpdateNodeConfigTokensColumns.php @@ -9,7 +9,7 @@ class UpdateNodeConfigTokensColumns extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('node_configuration_tokens', function (Blueprint $table) { $table->dropForeign(['node']); @@ -23,7 +23,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('node_configuration_tokens', function (Blueprint $table) { $table->dropForeign(['node_id']); diff --git a/database/migrations/2017_03_05_212803_DeleteServiceExecutableOption.php b/database/migrations/2017_03_05_212803_DeleteServiceExecutableOption.php index 6792f265a6..d697a3315f 100644 --- a/database/migrations/2017_03_05_212803_DeleteServiceExecutableOption.php +++ b/database/migrations/2017_03_05_212803_DeleteServiceExecutableOption.php @@ -9,7 +9,7 @@ class DeleteServiceExecutableOption extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('services', function (Blueprint $table) { $table->renameColumn('file', 'folder'); @@ -22,7 +22,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('services', function (Blueprint $table) { $table->string('executable')->after('folder'); diff --git a/database/migrations/2017_03_10_162934_AddNewServiceOptionsColumns.php b/database/migrations/2017_03_10_162934_AddNewServiceOptionsColumns.php index 385004fa46..06c04694cd 100644 --- a/database/migrations/2017_03_10_162934_AddNewServiceOptionsColumns.php +++ b/database/migrations/2017_03_10_162934_AddNewServiceOptionsColumns.php @@ -9,7 +9,7 @@ class AddNewServiceOptionsColumns extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('service_options', function (Blueprint $table) { $table->dropColumn('executable'); @@ -27,7 +27,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('service_options', function (Blueprint $table) { $table->dropForeign(['config_from']); diff --git a/database/migrations/2017_03_10_173607_MigrateToNewServiceSystem.php b/database/migrations/2017_03_10_173607_MigrateToNewServiceSystem.php index 7cf5707c44..40aef15246 100644 --- a/database/migrations/2017_03_10_173607_MigrateToNewServiceSystem.php +++ b/database/migrations/2017_03_10_173607_MigrateToNewServiceSystem.php @@ -7,7 +7,7 @@ class MigrateToNewServiceSystem extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { DB::transaction(function () { $service = DB::table('services')->where('author', config('pterodactyl.service.core'))->where('folder', 'srcds')->first(); @@ -32,7 +32,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { // Not doing reversals right now... } diff --git a/database/migrations/2017_03_11_215455_ChangeServiceVariablesValidationRules.php b/database/migrations/2017_03_11_215455_ChangeServiceVariablesValidationRules.php index 21fa514651..3e7e5f18b8 100644 --- a/database/migrations/2017_03_11_215455_ChangeServiceVariablesValidationRules.php +++ b/database/migrations/2017_03_11_215455_ChangeServiceVariablesValidationRules.php @@ -9,7 +9,7 @@ class ChangeServiceVariablesValidationRules extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('service_variables', function (Blueprint $table) { $table->renameColumn('regex', 'rules'); @@ -30,7 +30,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('service_variables', function (Blueprint $table) { $table->renameColumn('rules', 'regex'); diff --git a/database/migrations/2017_03_12_150648_MoveFunctionsFromFileToDatabase.php b/database/migrations/2017_03_12_150648_MoveFunctionsFromFileToDatabase.php index 3628ba7a4b..26599246cd 100644 --- a/database/migrations/2017_03_12_150648_MoveFunctionsFromFileToDatabase.php +++ b/database/migrations/2017_03_12_150648_MoveFunctionsFromFileToDatabase.php @@ -85,7 +85,7 @@ class Service extends Core { /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('services', function (Blueprint $table) { $table->text('index_file')->after('startup'); @@ -105,7 +105,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('services', function (Blueprint $table) { $table->dropColumn('index_file'); diff --git a/database/migrations/2017_03_14_175631_RenameServicePacksToSingluarPacks.php b/database/migrations/2017_03_14_175631_RenameServicePacksToSingluarPacks.php index d01012e41c..f73befdba6 100644 --- a/database/migrations/2017_03_14_175631_RenameServicePacksToSingluarPacks.php +++ b/database/migrations/2017_03_14_175631_RenameServicePacksToSingluarPacks.php @@ -9,7 +9,7 @@ class RenameServicePacksToSingluarPacks extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('service_packs', function (Blueprint $table) { $table->dropForeign(['option_id']); @@ -25,7 +25,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('packs', function (Blueprint $table) { $table->dropForeign(['option_id']); diff --git a/database/migrations/2017_03_14_200326_AddLockedStatusToTable.php b/database/migrations/2017_03_14_200326_AddLockedStatusToTable.php index b1a8ee3a04..b396954e06 100644 --- a/database/migrations/2017_03_14_200326_AddLockedStatusToTable.php +++ b/database/migrations/2017_03_14_200326_AddLockedStatusToTable.php @@ -9,7 +9,7 @@ class AddLockedStatusToTable extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('packs', function (Blueprint $table) { $table->boolean('locked')->default(false)->after('visible'); @@ -19,7 +19,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('packs', function (Blueprint $table) { $table->dropColumn('locked'); diff --git a/database/migrations/2017_03_16_181109_ReOrganizeDatabaseServersToDatabaseHost.php b/database/migrations/2017_03_16_181109_ReOrganizeDatabaseServersToDatabaseHost.php index a7166df9e0..c973faa555 100644 --- a/database/migrations/2017_03_16_181109_ReOrganizeDatabaseServersToDatabaseHost.php +++ b/database/migrations/2017_03_16_181109_ReOrganizeDatabaseServersToDatabaseHost.php @@ -9,7 +9,7 @@ class ReOrganizeDatabaseServersToDatabaseHost extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('database_servers', function (Blueprint $table) { $table->dropForeign(['linked_node']); @@ -27,7 +27,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('database_hosts', function (Blueprint $table) { $table->dropForeign(['node_id']); diff --git a/database/migrations/2017_03_16_181515_CleanupDatabasesDatabase.php b/database/migrations/2017_03_16_181515_CleanupDatabasesDatabase.php index bc6fb45c77..2b689c4817 100644 --- a/database/migrations/2017_03_16_181515_CleanupDatabasesDatabase.php +++ b/database/migrations/2017_03_16_181515_CleanupDatabasesDatabase.php @@ -9,7 +9,7 @@ class CleanupDatabasesDatabase extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('databases', function (Blueprint $table) { $table->dropForeign(['db_server']); @@ -23,7 +23,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('databases', function (Blueprint $table) { $table->dropForeign(['database_host_id']); diff --git a/database/migrations/2017_03_18_204953_AddForeignKeyToPacks.php b/database/migrations/2017_03_18_204953_AddForeignKeyToPacks.php index 3f26a1e34a..bdd1f1a543 100644 --- a/database/migrations/2017_03_18_204953_AddForeignKeyToPacks.php +++ b/database/migrations/2017_03_18_204953_AddForeignKeyToPacks.php @@ -9,7 +9,7 @@ class AddForeignKeyToPacks extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('servers', function (Blueprint $table) { $table->foreign('pack_id')->references('id')->on('packs'); @@ -19,7 +19,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('servers', function (Blueprint $table) { $table->dropForeign(['pack_id']); diff --git a/database/migrations/2017_03_31_221948_AddServerDescriptionColumn.php b/database/migrations/2017_03_31_221948_AddServerDescriptionColumn.php index e8ebcb20dc..69d0445823 100644 --- a/database/migrations/2017_03_31_221948_AddServerDescriptionColumn.php +++ b/database/migrations/2017_03_31_221948_AddServerDescriptionColumn.php @@ -9,7 +9,7 @@ class AddServerDescriptionColumn extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('servers', function (Blueprint $table) { $table->text('description')->after('name'); @@ -19,7 +19,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('servers', function (Blueprint $table) { $table->dropColumn('description'); diff --git a/database/migrations/2017_04_02_163232_DropDeletedAtColumnFromServers.php b/database/migrations/2017_04_02_163232_DropDeletedAtColumnFromServers.php index 3cd08f1a94..0c193192b2 100644 --- a/database/migrations/2017_04_02_163232_DropDeletedAtColumnFromServers.php +++ b/database/migrations/2017_04_02_163232_DropDeletedAtColumnFromServers.php @@ -9,7 +9,7 @@ class DropDeletedAtColumnFromServers extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('servers', function (Blueprint $table) { $table->dropColumn('deleted_at'); @@ -19,7 +19,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('servers', function (Blueprint $table) { $table->timestamp('deleted_at')->nullable(); diff --git a/database/migrations/2017_04_15_125021_UpgradeTaskSystem.php b/database/migrations/2017_04_15_125021_UpgradeTaskSystem.php index d069e1ba18..f5e48cfea7 100644 --- a/database/migrations/2017_04_15_125021_UpgradeTaskSystem.php +++ b/database/migrations/2017_04_15_125021_UpgradeTaskSystem.php @@ -10,7 +10,7 @@ class UpgradeTaskSystem extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('tasks', function (Blueprint $table) { $table->dropForeign(['server']); @@ -33,7 +33,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('tasks', function (Blueprint $table) { // $table->dropForeign(['server_id']); diff --git a/database/migrations/2017_04_20_171943_AddScriptsToServiceOptions.php b/database/migrations/2017_04_20_171943_AddScriptsToServiceOptions.php index ba2f57c41e..96a85be929 100644 --- a/database/migrations/2017_04_20_171943_AddScriptsToServiceOptions.php +++ b/database/migrations/2017_04_20_171943_AddScriptsToServiceOptions.php @@ -9,7 +9,7 @@ class AddScriptsToServiceOptions extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('service_options', function (Blueprint $table) { $table->text('script_install')->after('startup')->nullable(); @@ -22,7 +22,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('service_options', function (Blueprint $table) { $table->dropColumn('script_install'); diff --git a/database/migrations/2017_04_21_151432_AddServiceScriptTrackingToServers.php b/database/migrations/2017_04_21_151432_AddServiceScriptTrackingToServers.php index 2bc8f27b31..970b417730 100644 --- a/database/migrations/2017_04_21_151432_AddServiceScriptTrackingToServers.php +++ b/database/migrations/2017_04_21_151432_AddServiceScriptTrackingToServers.php @@ -9,7 +9,7 @@ class AddServiceScriptTrackingToServers extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('servers', function (Blueprint $table) { $table->boolean('skip_scripts')->default(false)->after('description'); @@ -19,7 +19,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('servers', function (Blueprint $table) { $table->dropColumn('skip_scripts'); diff --git a/database/migrations/2017_04_27_145300_AddCopyScriptFromColumn.php b/database/migrations/2017_04_27_145300_AddCopyScriptFromColumn.php index 514d17e1c0..8888600fba 100644 --- a/database/migrations/2017_04_27_145300_AddCopyScriptFromColumn.php +++ b/database/migrations/2017_04_27_145300_AddCopyScriptFromColumn.php @@ -9,7 +9,7 @@ class AddCopyScriptFromColumn extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('service_options', function (Blueprint $table) { $table->unsignedInteger('copy_script_from')->nullable()->after('script_container'); @@ -21,7 +21,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('service_options', function (Blueprint $table) { $table->dropForeign(['copy_script_from']); diff --git a/database/migrations/2017_04_27_223629_AddAbilityToDefineConnectionOverSSLWithDaemonBehindProxy.php b/database/migrations/2017_04_27_223629_AddAbilityToDefineConnectionOverSSLWithDaemonBehindProxy.php index aa5e04498b..96bb9aec55 100644 --- a/database/migrations/2017_04_27_223629_AddAbilityToDefineConnectionOverSSLWithDaemonBehindProxy.php +++ b/database/migrations/2017_04_27_223629_AddAbilityToDefineConnectionOverSSLWithDaemonBehindProxy.php @@ -9,7 +9,7 @@ class AddAbilityToDefineConnectionOverSSLWithDaemonBehindProxy extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('nodes', function (Blueprint $table) { $table->boolean('behind_proxy')->after('scheme')->default(false); @@ -19,7 +19,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('nodes', function (Blueprint $table) { $table->dropColumn('behind_proxy'); diff --git a/database/migrations/2017_05_01_141528_DeleteDownloadTable.php b/database/migrations/2017_05_01_141528_DeleteDownloadTable.php index 7dcae3c6f5..967c12615a 100644 --- a/database/migrations/2017_05_01_141528_DeleteDownloadTable.php +++ b/database/migrations/2017_05_01_141528_DeleteDownloadTable.php @@ -9,7 +9,7 @@ class DeleteDownloadTable extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::dropIfExists('downloads'); } @@ -17,7 +17,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::create('downloads', function (Blueprint $table) { $table->increments('id'); diff --git a/database/migrations/2017_05_01_141559_DeleteNodeConfigurationTable.php b/database/migrations/2017_05_01_141559_DeleteNodeConfigurationTable.php index 90c8c4b1ee..d230bc19ac 100644 --- a/database/migrations/2017_05_01_141559_DeleteNodeConfigurationTable.php +++ b/database/migrations/2017_05_01_141559_DeleteNodeConfigurationTable.php @@ -9,7 +9,7 @@ class DeleteNodeConfigurationTable extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::dropIfExists('node_configuration_tokens'); } @@ -17,7 +17,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::create('node_configuration_tokens', function (Blueprint $table) { $table->increments('id'); diff --git a/database/migrations/2017_06_10_152951_add_external_id_to_users.php b/database/migrations/2017_06_10_152951_add_external_id_to_users.php index 9ce5057e83..bccfb43fde 100644 --- a/database/migrations/2017_06_10_152951_add_external_id_to_users.php +++ b/database/migrations/2017_06_10_152951_add_external_id_to_users.php @@ -9,7 +9,7 @@ class AddExternalIdToUsers extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('users', function (Blueprint $table) { $table->unsignedInteger('external_id')->after('id')->nullable()->unique(); @@ -19,7 +19,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('users', function (Blueprint $table) { $table->dropColumn('external_id'); diff --git a/database/migrations/2017_06_25_133923_ChangeForeignKeyToBeOnCascadeDelete.php b/database/migrations/2017_06_25_133923_ChangeForeignKeyToBeOnCascadeDelete.php index a089ab4db3..6f36d0e050 100644 --- a/database/migrations/2017_06_25_133923_ChangeForeignKeyToBeOnCascadeDelete.php +++ b/database/migrations/2017_06_25_133923_ChangeForeignKeyToBeOnCascadeDelete.php @@ -9,7 +9,7 @@ class ChangeForeignKeyToBeOnCascadeDelete extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('api_permissions', function (Blueprint $table) { $table->dropForeign(['key_id']); @@ -21,7 +21,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('api_permissions', function (Blueprint $table) { $table->dropForeign(['key_id']); diff --git a/database/migrations/2017_07_08_152806_ChangeUserPermissionsToDeleteOnUserDeletion.php b/database/migrations/2017_07_08_152806_ChangeUserPermissionsToDeleteOnUserDeletion.php index 0bfc7d5270..10058c8ccf 100644 --- a/database/migrations/2017_07_08_152806_ChangeUserPermissionsToDeleteOnUserDeletion.php +++ b/database/migrations/2017_07_08_152806_ChangeUserPermissionsToDeleteOnUserDeletion.php @@ -9,7 +9,7 @@ class ChangeUserPermissionsToDeleteOnUserDeletion extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('permissions', function (Blueprint $table) { $table->dropForeign(['subuser_id']); @@ -29,7 +29,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('subusers', function (Blueprint $table) { $table->dropForeign(['user_id']); diff --git a/database/migrations/2017_07_08_154416_SetAllocationToReferenceNullOnServerDelete.php b/database/migrations/2017_07_08_154416_SetAllocationToReferenceNullOnServerDelete.php index fb156ba8c1..8ac6eccec5 100644 --- a/database/migrations/2017_07_08_154416_SetAllocationToReferenceNullOnServerDelete.php +++ b/database/migrations/2017_07_08_154416_SetAllocationToReferenceNullOnServerDelete.php @@ -9,7 +9,7 @@ class SetAllocationToReferenceNullOnServerDelete extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('allocations', function (Blueprint $table) { $table->dropForeign(['server_id']); @@ -21,7 +21,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('allocations', function (Blueprint $table) { $table->dropForeign(['server_id']); diff --git a/database/migrations/2017_07_08_154650_CascadeDeletionWhenAServerOrVariableIsDeleted.php b/database/migrations/2017_07_08_154650_CascadeDeletionWhenAServerOrVariableIsDeleted.php index 5ae9a29f95..ca5a4623f1 100644 --- a/database/migrations/2017_07_08_154650_CascadeDeletionWhenAServerOrVariableIsDeleted.php +++ b/database/migrations/2017_07_08_154650_CascadeDeletionWhenAServerOrVariableIsDeleted.php @@ -9,7 +9,7 @@ class CascadeDeletionWhenAServerOrVariableIsDeleted extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('server_variables', function (Blueprint $table) { $table->dropForeign(['server_id']); @@ -23,7 +23,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('server_variables', function (Blueprint $table) { $table->dropForeign(['server_id']); diff --git a/database/migrations/2017_07_24_194433_DeleteTaskWhenParentServerIsDeleted.php b/database/migrations/2017_07_24_194433_DeleteTaskWhenParentServerIsDeleted.php index 89e1102289..cf0a4bba17 100644 --- a/database/migrations/2017_07_24_194433_DeleteTaskWhenParentServerIsDeleted.php +++ b/database/migrations/2017_07_24_194433_DeleteTaskWhenParentServerIsDeleted.php @@ -9,7 +9,7 @@ class DeleteTaskWhenParentServerIsDeleted extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('tasks', function (Blueprint $table) { $table->dropForeign(['server_id']); @@ -21,7 +21,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { } } diff --git a/database/migrations/2017_08_05_115800_CascadeNullValuesForDatabaseHostWhenNodeIsDeleted.php b/database/migrations/2017_08_05_115800_CascadeNullValuesForDatabaseHostWhenNodeIsDeleted.php index a33b78af6c..0eabe77db5 100644 --- a/database/migrations/2017_08_05_115800_CascadeNullValuesForDatabaseHostWhenNodeIsDeleted.php +++ b/database/migrations/2017_08_05_115800_CascadeNullValuesForDatabaseHostWhenNodeIsDeleted.php @@ -9,7 +9,7 @@ class CascadeNullValuesForDatabaseHostWhenNodeIsDeleted extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('database_hosts', function (Blueprint $table) { $table->dropForeign(['node_id']); @@ -20,7 +20,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('database_hosts', function (Blueprint $table) { $table->dropForeign(['node_id']); diff --git a/database/migrations/2017_08_05_144104_AllowNegativeValuesForOverallocation.php b/database/migrations/2017_08_05_144104_AllowNegativeValuesForOverallocation.php index 77b7f984c9..3fb457dc47 100644 --- a/database/migrations/2017_08_05_144104_AllowNegativeValuesForOverallocation.php +++ b/database/migrations/2017_08_05_144104_AllowNegativeValuesForOverallocation.php @@ -9,7 +9,7 @@ class AllowNegativeValuesForOverallocation extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('nodes', function (Blueprint $table) { $table->integer('disk_overallocate')->default(0)->nullable(false)->change(); @@ -20,10 +20,10 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('nodes', function (Blueprint $table) { - DB::statement('ALTER TABLE nodes MODIFY disk_overallocate MEDIUMINT UNSIGNED NULL, + DB::statement('ALTER TABLE nodes MODIFY disk_overallocate MEDIUMINT UNSIGNED NULL, MODIFY memory_overallocate MEDIUMINT UNSIGNED NULL'); }); } diff --git a/database/migrations/2017_08_05_174811_SetAllocationUnqiueUsingMultipleFields.php b/database/migrations/2017_08_05_174811_SetAllocationUnqiueUsingMultipleFields.php index f7aab7c047..f2d8ad9bf8 100644 --- a/database/migrations/2017_08_05_174811_SetAllocationUnqiueUsingMultipleFields.php +++ b/database/migrations/2017_08_05_174811_SetAllocationUnqiueUsingMultipleFields.php @@ -9,7 +9,7 @@ class SetAllocationUnqiueUsingMultipleFields extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('allocations', function (Blueprint $table) { $table->unique(['node_id', 'ip', 'port']); @@ -19,7 +19,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('allocations', function (Blueprint $table) { $table->dropForeign(['node_id']); diff --git a/database/migrations/2017_08_15_214555_CascadeDeletionWhenAParentServiceIsDeleted.php b/database/migrations/2017_08_15_214555_CascadeDeletionWhenAParentServiceIsDeleted.php index 074f872e0d..fbea750bc6 100644 --- a/database/migrations/2017_08_15_214555_CascadeDeletionWhenAParentServiceIsDeleted.php +++ b/database/migrations/2017_08_15_214555_CascadeDeletionWhenAParentServiceIsDeleted.php @@ -9,7 +9,7 @@ class CascadeDeletionWhenAParentServiceIsDeleted extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('service_options', function (Blueprint $table) { $table->dropForeign(['service_id']); @@ -21,7 +21,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('service_options', function (Blueprint $table) { $table->dropForeign(['service_id']); diff --git a/database/migrations/2017_08_18_215428_RemovePackWhenParentServiceOptionIsDeleted.php b/database/migrations/2017_08_18_215428_RemovePackWhenParentServiceOptionIsDeleted.php index 1b8f1a5677..7c59d801e0 100644 --- a/database/migrations/2017_08_18_215428_RemovePackWhenParentServiceOptionIsDeleted.php +++ b/database/migrations/2017_08_18_215428_RemovePackWhenParentServiceOptionIsDeleted.php @@ -9,7 +9,7 @@ class RemovePackWhenParentServiceOptionIsDeleted extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('packs', function (Blueprint $table) { $table->dropForeign(['option_id']); @@ -21,7 +21,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('packs', function (Blueprint $table) { $table->dropForeign(['option_id']); diff --git a/database/migrations/2017_09_10_225749_RenameTasksTableForStructureRefactor.php b/database/migrations/2017_09_10_225749_RenameTasksTableForStructureRefactor.php index 12eada73c8..14f60b3b65 100644 --- a/database/migrations/2017_09_10_225749_RenameTasksTableForStructureRefactor.php +++ b/database/migrations/2017_09_10_225749_RenameTasksTableForStructureRefactor.php @@ -8,7 +8,7 @@ class RenameTasksTableForStructureRefactor extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::rename('tasks', 'tasks_old'); } @@ -16,7 +16,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::rename('tasks_old', 'tasks'); } diff --git a/database/migrations/2017_09_10_225941_CreateSchedulesTable.php b/database/migrations/2017_09_10_225941_CreateSchedulesTable.php index 3d5baa6d3e..588f48c8f5 100644 --- a/database/migrations/2017_09_10_225941_CreateSchedulesTable.php +++ b/database/migrations/2017_09_10_225941_CreateSchedulesTable.php @@ -9,7 +9,7 @@ class CreateSchedulesTable extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::create('schedules', function (Blueprint $table) { $table->increments('id'); @@ -32,7 +32,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::dropIfExists('schedules'); } diff --git a/database/migrations/2017_09_10_230309_CreateNewTasksTableForSchedules.php b/database/migrations/2017_09_10_230309_CreateNewTasksTableForSchedules.php index 9c225a834f..969c153612 100644 --- a/database/migrations/2017_09_10_230309_CreateNewTasksTableForSchedules.php +++ b/database/migrations/2017_09_10_230309_CreateNewTasksTableForSchedules.php @@ -9,7 +9,7 @@ class CreateNewTasksTableForSchedules extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::create('tasks', function (Blueprint $table) { $table->increments('id'); @@ -29,7 +29,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::dropIfExists('tasks'); } diff --git a/database/migrations/2017_09_11_002938_TransferOldTasksToNewScheduler.php b/database/migrations/2017_09_11_002938_TransferOldTasksToNewScheduler.php index 2a20ef10e7..4656e272e8 100644 --- a/database/migrations/2017_09_11_002938_TransferOldTasksToNewScheduler.php +++ b/database/migrations/2017_09_11_002938_TransferOldTasksToNewScheduler.php @@ -2,6 +2,7 @@ use Carbon\Carbon; use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Schema; use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; @@ -10,40 +11,40 @@ class TransferOldTasksToNewScheduler extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { - $tasks = DB::table('tasks_old')->get(); + DB::transaction(function () { + $tasks = DB::table('tasks_old')->get(); - DB::beginTransaction(); - $tasks->each(function ($task) { - $schedule = DB::table('schedules')->insertGetId([ - 'server_id' => $task->server_id, - 'name' => null, - 'cron_day_of_week' => $task->day_of_week, - 'cron_day_of_month' => $task->day_of_month, - 'cron_hour' => $task->hour, - 'cron_minute' => $task->minute, - 'is_active' => (bool) $task->active, - 'is_processing' => false, - 'last_run_at' => $task->last_run, - 'next_run_at' => $task->next_run, - 'created_at' => $task->created_at, - 'updated_at' => Carbon::now()->toDateTimeString(), - ]); + $tasks->each(function ($task) { + $schedule = DB::table('schedules')->insertGetId([ + 'server_id' => $task->server_id, + 'name' => null, + 'cron_day_of_week' => $task->day_of_week, + 'cron_day_of_month' => $task->day_of_month, + 'cron_hour' => $task->hour, + 'cron_minute' => $task->minute, + 'is_active' => (bool) $task->active, + 'is_processing' => false, + 'last_run_at' => $task->last_run, + 'next_run_at' => $task->next_run, + 'created_at' => $task->created_at, + 'updated_at' => Carbon::now()->toDateTimeString(), + ]); - DB::table('tasks')->insert([ - 'schedule_id' => $schedule, - 'sequence_id' => 1, - 'action' => $task->action, - 'payload' => $task->data, - 'time_offset' => 0, - 'is_queued' => false, - 'updated_at' => Carbon::now()->toDateTimeString(), - 'created_at' => Carbon::now()->toDateTimeString(), - ]); + DB::table('tasks')->insert([ + 'schedule_id' => $schedule, + 'sequence_id' => 1, + 'action' => $task->action, + 'payload' => $task->data, + 'time_offset' => 0, + 'is_queued' => false, + 'updated_at' => Carbon::now()->toDateTimeString(), + 'created_at' => Carbon::now()->toDateTimeString(), + ]); - DB::table('tasks_old')->delete($task->id); - DB::commit(); + DB::table('tasks_old')->delete($task->id); + }); }); Schema::dropIfExists('tasks_old'); @@ -52,7 +53,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::create('tasks_old', function (Blueprint $table) { $table->increments('id'); diff --git a/database/migrations/2017_09_13_211810_UpdateOldPermissionsToPointToNewScheduleSystem.php b/database/migrations/2017_09_13_211810_UpdateOldPermissionsToPointToNewScheduleSystem.php index ba3a8bac0e..7c0c574470 100644 --- a/database/migrations/2017_09_13_211810_UpdateOldPermissionsToPointToNewScheduleSystem.php +++ b/database/migrations/2017_09_13_211810_UpdateOldPermissionsToPointToNewScheduleSystem.php @@ -8,7 +8,7 @@ class UpdateOldPermissionsToPointToNewScheduleSystem extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { $permissions = DB::table('permissions')->where('permission', 'like', '%-task%')->get(); foreach ($permissions as $record) { @@ -26,7 +26,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { $permissions = DB::table('permissions')->where('permission', 'like', '%-schedule%')->get(); foreach ($permissions as $record) { diff --git a/database/migrations/2017_09_23_170933_CreateDaemonKeysTable.php b/database/migrations/2017_09_23_170933_CreateDaemonKeysTable.php index cfbfc88b04..64ff02666a 100644 --- a/database/migrations/2017_09_23_170933_CreateDaemonKeysTable.php +++ b/database/migrations/2017_09_23_170933_CreateDaemonKeysTable.php @@ -9,7 +9,7 @@ class CreateDaemonKeysTable extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::create('daemon_keys', function (Blueprint $table) { $table->increments('id'); @@ -28,7 +28,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::dropIfExists('daemon_keys'); } diff --git a/database/migrations/2017_09_23_173628_RemoveDaemonSecretFromServersTable.php b/database/migrations/2017_09_23_173628_RemoveDaemonSecretFromServersTable.php index 84cb2d92bb..b284905a0f 100644 --- a/database/migrations/2017_09_23_173628_RemoveDaemonSecretFromServersTable.php +++ b/database/migrations/2017_09_23_173628_RemoveDaemonSecretFromServersTable.php @@ -12,7 +12,7 @@ class RemoveDaemonSecretFromServersTable extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { $inserts = []; @@ -41,7 +41,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('servers', function (Blueprint $table) { $table->char('daemonSecret', 36)->after('startup')->unique(); diff --git a/database/migrations/2017_09_23_185022_RemoveDaemonSecretFromSubusersTable.php b/database/migrations/2017_09_23_185022_RemoveDaemonSecretFromSubusersTable.php index d4d2dd695e..9ea90cff28 100644 --- a/database/migrations/2017_09_23_185022_RemoveDaemonSecretFromSubusersTable.php +++ b/database/migrations/2017_09_23_185022_RemoveDaemonSecretFromSubusersTable.php @@ -1,6 +1,7 @@ get(); @@ -39,7 +40,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('subusers', function (Blueprint $table) { $table->char('daemonSecret', 36)->after('server_id'); diff --git a/database/migrations/2017_10_02_202000_ChangeServicesToUseAMoreUniqueIdentifier.php b/database/migrations/2017_10_02_202000_ChangeServicesToUseAMoreUniqueIdentifier.php index dffa7687aa..aae62921a3 100644 --- a/database/migrations/2017_10_02_202000_ChangeServicesToUseAMoreUniqueIdentifier.php +++ b/database/migrations/2017_10_02_202000_ChangeServicesToUseAMoreUniqueIdentifier.php @@ -11,7 +11,7 @@ class ChangeServicesToUseAMoreUniqueIdentifier extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('services', function (Blueprint $table) { $table->dropUnique(['name']); @@ -39,7 +39,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('services', function (Blueprint $table) { $table->dropColumn('uuid'); diff --git a/database/migrations/2017_10_02_202007_ChangeToABetterUniqueServiceConfiguration.php b/database/migrations/2017_10_02_202007_ChangeToABetterUniqueServiceConfiguration.php index 5c9df79a5e..679a8b5e02 100644 --- a/database/migrations/2017_10_02_202007_ChangeToABetterUniqueServiceConfiguration.php +++ b/database/migrations/2017_10_02_202007_ChangeToABetterUniqueServiceConfiguration.php @@ -11,7 +11,7 @@ class ChangeToABetterUniqueServiceConfiguration extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('service_options', function (Blueprint $table) { $table->char('uuid', 36)->after('id'); @@ -40,7 +40,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('service_options', function (Blueprint $table) { $table->dropColumn('uuid'); diff --git a/database/migrations/2017_10_03_233202_CascadeDeletionWhenServiceOptionIsDeleted.php b/database/migrations/2017_10_03_233202_CascadeDeletionWhenServiceOptionIsDeleted.php index 3b19e3d998..9a64abf074 100644 --- a/database/migrations/2017_10_03_233202_CascadeDeletionWhenServiceOptionIsDeleted.php +++ b/database/migrations/2017_10_03_233202_CascadeDeletionWhenServiceOptionIsDeleted.php @@ -9,7 +9,7 @@ class CascadeDeletionWhenServiceOptionIsDeleted extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('service_variables', function (Blueprint $table) { $table->dropForeign(['option_id']); @@ -21,7 +21,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('service_variables', function (Blueprint $table) { $table->dropForeign(['option_id']); diff --git a/database/migrations/2017_10_06_214026_ServicesToNestsConversion.php b/database/migrations/2017_10_06_214026_ServicesToNestsConversion.php index e7b70136ce..cea7460e63 100644 --- a/database/migrations/2017_10_06_214026_ServicesToNestsConversion.php +++ b/database/migrations/2017_10_06_214026_ServicesToNestsConversion.php @@ -1,5 +1,6 @@ dropUnique(['username']); @@ -22,7 +22,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('servers', function (Blueprint $table) { $table->string('username')->nullable()->after('image')->unique(); diff --git a/database/migrations/2017_11_11_161922_Add2FaLastAuthorizationTimeColumn.php b/database/migrations/2017_11_11_161922_Add2FaLastAuthorizationTimeColumn.php index b90b150bd4..b02e326c64 100644 --- a/database/migrations/2017_11_11_161922_Add2FaLastAuthorizationTimeColumn.php +++ b/database/migrations/2017_11_11_161922_Add2FaLastAuthorizationTimeColumn.php @@ -12,7 +12,7 @@ class Add2FaLastAuthorizationTimeColumn extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('users', function (Blueprint $table) { $table->text('totp_secret')->nullable()->change(); @@ -36,7 +36,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { DB::transaction(function () { DB::table('users')->get()->each(function ($user) { diff --git a/database/migrations/2017_11_19_122708_MigratePubPrivFormatToSingleKey.php b/database/migrations/2017_11_19_122708_MigratePubPrivFormatToSingleKey.php index c2947ee07c..229827dbcc 100644 --- a/database/migrations/2017_11_19_122708_MigratePubPrivFormatToSingleKey.php +++ b/database/migrations/2017_11_19_122708_MigratePubPrivFormatToSingleKey.php @@ -1,5 +1,6 @@ get()->each(function ($item) { try { $decrypted = Crypt::decrypt($item->secret); - } catch (DecryptException $exception) { + } catch (DecryptException) { $decrypted = str_random(32); } finally { DB::table('api_keys')->where('id', $item->id)->update([ @@ -30,27 +31,41 @@ public function up() Schema::table('api_keys', function (Blueprint $table) { $table->dropColumn('public'); - $table->string('secret', 32)->change(); + $table->renameColumn('secret', 'token'); }); - DB::statement('ALTER TABLE `api_keys` CHANGE `secret` `token` CHAR(32) NOT NULL, ADD UNIQUE INDEX `api_keys_token_unique` (`token`(32))'); + Schema::table('api_keys', function (Blueprint $table) { + $table->char('token', 32)->change(); + $table->unique('token'); + }); } /** * Reverse the migrations. */ - public function down() + public function down(): void { - DB::statement('ALTER TABLE `api_keys` CHANGE `token` `secret` TEXT, DROP INDEX `api_keys_token_unique`'); + Schema::table('api_keys', function (Blueprint $table) { + $table->dropUnique(['token']); + $table->renameColumn('token', 'secret'); + }); Schema::table('api_keys', function (Blueprint $table) { + $table->dropUnique('token'); + $table->text('token')->change(); + }); + + Schema::table('api_keys', function (Blueprint $table) { + $table->renameColumn('token', 'secret'); + + $table->text('secret')->nullable()->change(); $table->char('public', 16)->after('user_id'); }); DB::transaction(function () { DB::table('api_keys')->get()->each(function ($item) { DB::table('api_keys')->where('id', $item->id)->update([ - 'public' => str_random(16), + 'public' => Str::random(16), 'secret' => Crypt::encrypt($item->secret), ]); }); diff --git a/database/migrations/2017_12_04_184012_DropAllocationsWhenNodeIsDeleted.php b/database/migrations/2017_12_04_184012_DropAllocationsWhenNodeIsDeleted.php index d28109598c..9a0cc9114f 100644 --- a/database/migrations/2017_12_04_184012_DropAllocationsWhenNodeIsDeleted.php +++ b/database/migrations/2017_12_04_184012_DropAllocationsWhenNodeIsDeleted.php @@ -9,7 +9,7 @@ class DropAllocationsWhenNodeIsDeleted extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('allocations', function (Blueprint $table) { $table->dropForeign(['node_id']); @@ -21,7 +21,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('allocations', function (Blueprint $table) { $table->dropForeign(['node_id']); diff --git a/database/migrations/2017_12_12_220426_MigrateSettingsTableToNewFormat.php b/database/migrations/2017_12_12_220426_MigrateSettingsTableToNewFormat.php index 1bdaf6477b..8d639a37a9 100644 --- a/database/migrations/2017_12_12_220426_MigrateSettingsTableToNewFormat.php +++ b/database/migrations/2017_12_12_220426_MigrateSettingsTableToNewFormat.php @@ -10,7 +10,7 @@ class MigrateSettingsTableToNewFormat extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { DB::table('settings')->truncate(); Schema::table('settings', function (Blueprint $table) { @@ -21,7 +21,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('settings', function (Blueprint $table) { $table->dropColumn('id'); diff --git a/database/migrations/2018_01_01_122821_AllowNegativeValuesForServerSwap.php b/database/migrations/2018_01_01_122821_AllowNegativeValuesForServerSwap.php index 8f9938da15..7ccae5d61a 100644 --- a/database/migrations/2018_01_01_122821_AllowNegativeValuesForServerSwap.php +++ b/database/migrations/2018_01_01_122821_AllowNegativeValuesForServerSwap.php @@ -8,10 +8,8 @@ class AllowNegativeValuesForServerSwap extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('servers', function (Blueprint $table) { $table->integer('swap')->change(); @@ -20,10 +18,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('servers', function (Blueprint $table) { $table->unsignedInteger('swap')->change(); diff --git a/database/migrations/2018_01_11_213943_AddApiKeyPermissionColumns.php b/database/migrations/2018_01_11_213943_AddApiKeyPermissionColumns.php index adc6d2648f..118a422f4a 100644 --- a/database/migrations/2018_01_11_213943_AddApiKeyPermissionColumns.php +++ b/database/migrations/2018_01_11_213943_AddApiKeyPermissionColumns.php @@ -8,10 +8,8 @@ class AddApiKeyPermissionColumns extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::dropIfExists('api_permissions'); @@ -31,10 +29,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::create('api_permissions', function (Blueprint $table) { $table->increments('id'); diff --git a/database/migrations/2018_01_13_142012_SetupTableForKeyEncryption.php b/database/migrations/2018_01_13_142012_SetupTableForKeyEncryption.php index 1d36b3648b..d7e33210d6 100644 --- a/database/migrations/2018_01_13_142012_SetupTableForKeyEncryption.php +++ b/database/migrations/2018_01_13_142012_SetupTableForKeyEncryption.php @@ -9,12 +9,10 @@ class SetupTableForKeyEncryption extends Migration /** * Run the migrations. * - * @return void - * * @throws \Exception * @throws \Throwable */ - public function up() + public function up(): void { Schema::table('api_keys', function (Blueprint $table) { $table->char('identifier', 16)->nullable()->unique()->after('user_id'); @@ -29,12 +27,10 @@ public function up() /** * Reverse the migrations. * - * @return void - * * @throws \Exception * @throws \Throwable */ - public function down() + public function down(): void { Schema::table('api_keys', function (Blueprint $table) { $table->dropColumn('identifier'); diff --git a/database/migrations/2018_01_13_145209_AddLastUsedAtColumn.php b/database/migrations/2018_01_13_145209_AddLastUsedAtColumn.php index e0f86b9de8..f78f7a5d16 100644 --- a/database/migrations/2018_01_13_145209_AddLastUsedAtColumn.php +++ b/database/migrations/2018_01_13_145209_AddLastUsedAtColumn.php @@ -8,10 +8,8 @@ class AddLastUsedAtColumn extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('api_keys', function (Blueprint $table) { $table->unsignedTinyInteger('key_type')->after('user_id')->default(0); @@ -28,10 +26,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('api_keys', function (Blueprint $table) { $table->timestamp('expires_at')->after('memo')->nullable(); diff --git a/database/migrations/2018_02_04_145617_AllowTextInUserExternalId.php b/database/migrations/2018_02_04_145617_AllowTextInUserExternalId.php index 6a4a04e7d8..6166f016e5 100644 --- a/database/migrations/2018_02_04_145617_AllowTextInUserExternalId.php +++ b/database/migrations/2018_02_04_145617_AllowTextInUserExternalId.php @@ -8,10 +8,8 @@ class AllowTextInUserExternalId extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('users', function (Blueprint $table) { $table->string('external_id')->nullable()->change(); @@ -20,10 +18,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('users', function (Blueprint $table) { $table->unsignedInteger('external_id')->change(); diff --git a/database/migrations/2018_02_10_151150_remove_unique_index_on_external_id_column.php b/database/migrations/2018_02_10_151150_remove_unique_index_on_external_id_column.php index b587cdcb06..64dbaf0dca 100644 --- a/database/migrations/2018_02_10_151150_remove_unique_index_on_external_id_column.php +++ b/database/migrations/2018_02_10_151150_remove_unique_index_on_external_id_column.php @@ -8,10 +8,8 @@ class RemoveUniqueIndexOnExternalIdColumn extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('users', function (Blueprint $table) { $table->dropUnique(['external_id']); @@ -20,10 +18,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('users', function (Blueprint $table) { $table->unique(['external_id']); diff --git a/database/migrations/2018_02_17_134254_ensure_unique_allocation_id_on_servers_table.php b/database/migrations/2018_02_17_134254_ensure_unique_allocation_id_on_servers_table.php index bff7bbfb06..99f1db4549 100644 --- a/database/migrations/2018_02_17_134254_ensure_unique_allocation_id_on_servers_table.php +++ b/database/migrations/2018_02_17_134254_ensure_unique_allocation_id_on_servers_table.php @@ -8,10 +8,8 @@ class EnsureUniqueAllocationIdOnServersTable extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('servers', function (Blueprint $table) { $table->unique(['allocation_id']); @@ -20,10 +18,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('servers', function (Blueprint $table) { $table->dropForeign(['allocation_id']); diff --git a/database/migrations/2018_02_24_112356_add_external_id_column_to_servers_table.php b/database/migrations/2018_02_24_112356_add_external_id_column_to_servers_table.php index 2c8af99e22..c7d0f8fd45 100644 --- a/database/migrations/2018_02_24_112356_add_external_id_column_to_servers_table.php +++ b/database/migrations/2018_02_24_112356_add_external_id_column_to_servers_table.php @@ -8,10 +8,8 @@ class AddExternalIdColumnToServersTable extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('servers', function (Blueprint $table) { $table->string('external_id')->after('id')->nullable()->unique(); @@ -20,10 +18,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('servers', function (Blueprint $table) { $table->dropColumn('external_id'); diff --git a/database/migrations/2018_02_25_160152_remove_default_null_value_on_table.php b/database/migrations/2018_02_25_160152_remove_default_null_value_on_table.php index 6469867f25..e432e56dd1 100644 --- a/database/migrations/2018_02_25_160152_remove_default_null_value_on_table.php +++ b/database/migrations/2018_02_25_160152_remove_default_null_value_on_table.php @@ -13,7 +13,7 @@ class RemoveDefaultNullValueOnTable extends Migration * @throws \Exception * @throws \Throwable */ - public function up() + public function up(): void { Schema::table('users', function (Blueprint $table) { $table->string('external_id')->default(null)->change(); @@ -28,10 +28,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { // This should not be rolled back. } diff --git a/database/migrations/2018_02_25_160604_define_unique_index_on_users_external_id.php b/database/migrations/2018_02_25_160604_define_unique_index_on_users_external_id.php index 0a9b8afe2b..38469af237 100644 --- a/database/migrations/2018_02_25_160604_define_unique_index_on_users_external_id.php +++ b/database/migrations/2018_02_25_160604_define_unique_index_on_users_external_id.php @@ -8,10 +8,8 @@ class DefineUniqueIndexOnUsersExternalId extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('users', function (Blueprint $table) { $table->index(['external_id']); @@ -20,10 +18,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('users', function (Blueprint $table) { $table->dropIndex(['external_id']); diff --git a/database/migrations/2018_03_01_192831_add_database_and_port_limit_columns_to_servers_table.php b/database/migrations/2018_03_01_192831_add_database_and_port_limit_columns_to_servers_table.php index 4e85e8aebb..00fbd11c2b 100644 --- a/database/migrations/2018_03_01_192831_add_database_and_port_limit_columns_to_servers_table.php +++ b/database/migrations/2018_03_01_192831_add_database_and_port_limit_columns_to_servers_table.php @@ -8,10 +8,8 @@ class AddDatabaseAndPortLimitColumnsToServersTable extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('servers', function (Blueprint $table) { $table->unsignedInteger('database_limit')->after('installed')->nullable()->default(0); @@ -21,10 +19,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('servers', function (Blueprint $table) { $table->dropColumn(['database_limit', 'allocation_limit']); diff --git a/database/migrations/2018_03_15_124536_add_description_to_nodes.php b/database/migrations/2018_03_15_124536_add_description_to_nodes.php index 7208a4207c..a5c1b75421 100644 --- a/database/migrations/2018_03_15_124536_add_description_to_nodes.php +++ b/database/migrations/2018_03_15_124536_add_description_to_nodes.php @@ -8,10 +8,8 @@ class AddDescriptionToNodes extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('nodes', function (Blueprint $table) { $table->text('description')->after('name'); @@ -20,10 +18,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('nodes', function (Blueprint $table) { $table->dropColumn('description'); diff --git a/database/migrations/2018_05_04_123826_add_maintenance_to_nodes.php b/database/migrations/2018_05_04_123826_add_maintenance_to_nodes.php index 04fdf000f3..e85eca8cda 100644 --- a/database/migrations/2018_05_04_123826_add_maintenance_to_nodes.php +++ b/database/migrations/2018_05_04_123826_add_maintenance_to_nodes.php @@ -8,10 +8,8 @@ class AddMaintenanceToNodes extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('nodes', function (Blueprint $table) { $table->boolean('maintenance_mode')->after('behind_proxy')->default(false); @@ -20,10 +18,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('nodes', function (Blueprint $table) { $table->dropColumn('maintenance_mode'); diff --git a/database/migrations/2018_09_03_143756_allow_egg_variables_to_have_longer_values.php b/database/migrations/2018_09_03_143756_allow_egg_variables_to_have_longer_values.php index 1996509407..e7a4089fa0 100644 --- a/database/migrations/2018_09_03_143756_allow_egg_variables_to_have_longer_values.php +++ b/database/migrations/2018_09_03_143756_allow_egg_variables_to_have_longer_values.php @@ -8,10 +8,8 @@ class AllowEggVariablesToHaveLongerValues extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('egg_variables', function (Blueprint $table) { $table->text('default_value')->change(); @@ -20,10 +18,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('egg_variables', function (Blueprint $table) { $table->string('default_value')->change(); diff --git a/database/migrations/2018_09_03_144005_allow_server_variables_to_have_longer_values.php b/database/migrations/2018_09_03_144005_allow_server_variables_to_have_longer_values.php index cc90e0e068..a1d581819b 100644 --- a/database/migrations/2018_09_03_144005_allow_server_variables_to_have_longer_values.php +++ b/database/migrations/2018_09_03_144005_allow_server_variables_to_have_longer_values.php @@ -8,10 +8,8 @@ class AllowServerVariablesToHaveLongerValues extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('server_variables', function (Blueprint $table) { $table->text('variable_value')->change(); @@ -20,10 +18,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('server_variables', function (Blueprint $table) { $table->string('variable_value')->change(); diff --git a/database/migrations/2019_03_02_142328_set_allocation_limit_default_null.php b/database/migrations/2019_03_02_142328_set_allocation_limit_default_null.php index d91ce63720..6d43197dea 100644 --- a/database/migrations/2019_03_02_142328_set_allocation_limit_default_null.php +++ b/database/migrations/2019_03_02_142328_set_allocation_limit_default_null.php @@ -8,10 +8,8 @@ class SetAllocationLimitDefaultNull extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('servers', function (Blueprint $table) { $table->unsignedInteger('allocation_limit')->nullable()->default(null)->change(); @@ -20,10 +18,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('servers', function (Blueprint $table) { $table->unsignedInteger('allocation_limit')->nullable()->default(0)->change(); diff --git a/database/migrations/2019_03_02_151321_fix_unique_index_to_account_for_host.php b/database/migrations/2019_03_02_151321_fix_unique_index_to_account_for_host.php index 59425aee76..de110a06bc 100644 --- a/database/migrations/2019_03_02_151321_fix_unique_index_to_account_for_host.php +++ b/database/migrations/2019_03_02_151321_fix_unique_index_to_account_for_host.php @@ -8,10 +8,8 @@ class FixUniqueIndexToAccountForHost extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('databases', function (Blueprint $table) { $table->dropUnique(['database']); @@ -24,10 +22,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('databases', function (Blueprint $table) { $table->dropForeign(['database_host_id']); diff --git a/database/migrations/2020_03_22_163911_merge_permissions_table_into_subusers.php b/database/migrations/2020_03_22_163911_merge_permissions_table_into_subusers.php index 27d26674fa..b71189fe02 100644 --- a/database/migrations/2020_03_22_163911_merge_permissions_table_into_subusers.php +++ b/database/migrations/2020_03_22_163911_merge_permissions_table_into_subusers.php @@ -61,10 +61,8 @@ class MergePermissionsTableIntoSubusers extends Migration /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('subusers', function (Blueprint $table) { $table->json('permissions')->nullable()->after('server_id'); @@ -72,7 +70,12 @@ public function up() $cursor = DB::table('permissions') ->select(['subuser_id']) - ->selectRaw('GROUP_CONCAT(permission) as permissions') + ->when(DB::getPdo()->getAttribute(PDO::ATTR_DRIVER_NAME) === 'mysql', function ($query) { + $query->selectRaw('group_concat(permission) as permissions'); + }) + ->when(DB::getPdo()->getAttribute(PDO::ATTR_DRIVER_NAME) === 'pgsql', function ($query) { + $query->selectRaw("string_agg(permission, ',') as permissions"); + }) ->from('permissions') ->groupBy(['subuser_id']) ->cursor(); @@ -98,10 +101,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { $flipped = array_flip(array_filter(self::$permissionsMap)); diff --git a/database/migrations/2020_03_22_164814_drop_permissions_table.php b/database/migrations/2020_03_22_164814_drop_permissions_table.php index da9d677a8d..030a8a6bad 100644 --- a/database/migrations/2020_03_22_164814_drop_permissions_table.php +++ b/database/migrations/2020_03_22_164814_drop_permissions_table.php @@ -8,20 +8,16 @@ class DropPermissionsTable extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::dropIfExists('permissions'); } /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::create('permissions', function (Blueprint $table) { $table->increments('id'); diff --git a/database/migrations/2020_04_03_203624_add_threads_column_to_servers_table.php b/database/migrations/2020_04_03_203624_add_threads_column_to_servers_table.php index 9b0202cab2..d4c08c5e5f 100644 --- a/database/migrations/2020_04_03_203624_add_threads_column_to_servers_table.php +++ b/database/migrations/2020_04_03_203624_add_threads_column_to_servers_table.php @@ -8,10 +8,8 @@ class AddThreadsColumnToServersTable extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('servers', function (Blueprint $table) { $table->string('threads')->nullable()->after('cpu'); @@ -20,10 +18,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('servers', function (Blueprint $table) { $table->dropColumn('threads'); diff --git a/database/migrations/2020_04_03_230614_create_backups_table.php b/database/migrations/2020_04_03_230614_create_backups_table.php index daa35dd3b5..a8c28d96d5 100644 --- a/database/migrations/2020_04_03_230614_create_backups_table.php +++ b/database/migrations/2020_04_03_230614_create_backups_table.php @@ -9,10 +9,8 @@ class CreateBackupsTable extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { $db = config('database.default'); // There exists a backups plugin for the 0.7 version of the Panel. However, it didn't properly @@ -49,10 +47,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::dropIfExists('backups'); } diff --git a/database/migrations/2020_04_04_131016_add_table_server_transfers.php b/database/migrations/2020_04_04_131016_add_table_server_transfers.php index 096b5384f4..c9f3e849a9 100644 --- a/database/migrations/2020_04_04_131016_add_table_server_transfers.php +++ b/database/migrations/2020_04_04_131016_add_table_server_transfers.php @@ -8,10 +8,8 @@ class AddTableServerTransfers extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { // Nuclear approach to whatever plugins are out there and not properly namespacing their own tables // leading to constant support requests from people... @@ -20,13 +18,13 @@ public function up() Schema::create('server_transfers', function (Blueprint $table) { $table->increments('id'); $table->integer('server_id')->unsigned(); - $table->tinyInteger('successful')->unsigned()->default(0); + $table->boolean('successful')->unsigned()->default(0); $table->integer('old_node')->unsigned(); $table->integer('new_node')->unsigned(); $table->integer('old_allocation')->unsigned(); $table->integer('new_allocation')->unsigned(); - $table->string('old_additional_allocations')->nullable(); - $table->string('new_additional_allocations')->nullable(); + $table->json('old_additional_allocations')->nullable(); + $table->json('new_additional_allocations')->nullable(); $table->timestamps(); $table->foreign('server_id')->references('id')->on('servers')->onDelete('cascade'); @@ -35,10 +33,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::dropIfExists('server_transfers'); } 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 6544679fe8..a69dafc895 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 @@ -13,11 +13,9 @@ class StoreNodeTokensAsEncryptedValue extends Migration /** * Run the migrations. * - * @return void - * * @throws \Exception */ - public function up() + public function up(): void { Schema::table('nodes', function (Blueprint $table) { $table->dropUnique(['daemonSecret']); @@ -26,7 +24,8 @@ public function up() Schema::table('nodes', function (Blueprint $table) { $table->char('uuid', 36)->after('id'); $table->char('daemon_token_id', 16)->after('upload_size'); - $table->renameColumn('daemonSecret', 'daemon_token'); + + $table->renameColumn('`daemonSecret`', 'daemon_token'); }); Schema::table('nodes', function (Blueprint $table) { @@ -53,10 +52,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { DB::transaction(function () { /** @var \Illuminate\Contracts\Encryption\Encrypter $encrypter */ diff --git a/database/migrations/2020_04_17_203438_allow_nullable_descriptions.php b/database/migrations/2020_04_17_203438_allow_nullable_descriptions.php index dfd55fb423..8c2c149cfd 100644 --- a/database/migrations/2020_04_17_203438_allow_nullable_descriptions.php +++ b/database/migrations/2020_04_17_203438_allow_nullable_descriptions.php @@ -8,10 +8,8 @@ class AllowNullableDescriptions extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('eggs', function (Blueprint $table) { $table->text('description')->nullable()->change(); @@ -32,10 +30,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('eggs', function (Blueprint $table) { $table->text('description')->nullable(false)->change(); diff --git a/database/migrations/2020_04_22_055500_add_max_connections_column.php b/database/migrations/2020_04_22_055500_add_max_connections_column.php index 02253dfd75..57604117c5 100644 --- a/database/migrations/2020_04_22_055500_add_max_connections_column.php +++ b/database/migrations/2020_04_22_055500_add_max_connections_column.php @@ -8,10 +8,8 @@ class AddMaxConnectionsColumn extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('databases', function (Blueprint $table) { $table->integer('max_connections')->nullable()->default(0)->after('password'); @@ -20,10 +18,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('databases', function (Blueprint $table) { $table->dropColumn('max_connections'); diff --git a/database/migrations/2020_04_26_111208_add_backup_limit_to_servers.php b/database/migrations/2020_04_26_111208_add_backup_limit_to_servers.php index b0f859c9fc..af1b72e649 100644 --- a/database/migrations/2020_04_26_111208_add_backup_limit_to_servers.php +++ b/database/migrations/2020_04_26_111208_add_backup_limit_to_servers.php @@ -9,10 +9,8 @@ class AddBackupLimitToServers extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { $db = config('database.default'); // Same as in the backups migration, we need to handle that plugin messing with the data structure @@ -35,10 +33,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('servers', function (Blueprint $table) { $table->dropColumn('backup_limit'); diff --git a/database/migrations/2020_05_20_234655_add_mounts_table.php b/database/migrations/2020_05_20_234655_add_mounts_table.php index 09846a0a5d..db3b409d8c 100644 --- a/database/migrations/2020_05_20_234655_add_mounts_table.php +++ b/database/migrations/2020_05_20_234655_add_mounts_table.php @@ -8,10 +8,8 @@ class AddMountsTable extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::create('mounts', function (Blueprint $table) { $table->increments('id')->unique(); @@ -41,10 +39,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::dropIfExists('mount_node'); Schema::dropIfExists('egg_mount'); diff --git a/database/migrations/2020_05_21_192756_add_mount_server_table.php b/database/migrations/2020_05_21_192756_add_mount_server_table.php index 682bd578d9..7d8e9438b2 100644 --- a/database/migrations/2020_05_21_192756_add_mount_server_table.php +++ b/database/migrations/2020_05_21_192756_add_mount_server_table.php @@ -8,10 +8,8 @@ class AddMountServerTable extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::create('mount_server', function (Blueprint $table) { $table->integer('server_id'); @@ -23,10 +21,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::dropIfExists('mount_server'); } diff --git a/database/migrations/2020_07_02_213612_create_user_recovery_tokens_table.php b/database/migrations/2020_07_02_213612_create_user_recovery_tokens_table.php index 9b0743af23..11a6f513c7 100644 --- a/database/migrations/2020_07_02_213612_create_user_recovery_tokens_table.php +++ b/database/migrations/2020_07_02_213612_create_user_recovery_tokens_table.php @@ -8,10 +8,8 @@ class CreateUserRecoveryTokensTable extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::create('recovery_tokens', function (Blueprint $table) { $table->id(); @@ -25,10 +23,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::dropIfExists('recovery_tokens'); } diff --git a/database/migrations/2020_07_09_201845_add_notes_column_for_allocations.php b/database/migrations/2020_07_09_201845_add_notes_column_for_allocations.php index 711495edfd..a93b48053d 100644 --- a/database/migrations/2020_07_09_201845_add_notes_column_for_allocations.php +++ b/database/migrations/2020_07_09_201845_add_notes_column_for_allocations.php @@ -8,10 +8,8 @@ class AddNotesColumnForAllocations extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('allocations', function (Blueprint $table) { $table->string('notes')->nullable()->after('server_id'); @@ -20,10 +18,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('allocations', function (Blueprint $table) { $table->dropColumn('notes'); diff --git a/database/migrations/2020_08_20_205533_add_backup_state_column_to_backups.php b/database/migrations/2020_08_20_205533_add_backup_state_column_to_backups.php index 9e6faa42b5..4ac99feddd 100644 --- a/database/migrations/2020_08_20_205533_add_backup_state_column_to_backups.php +++ b/database/migrations/2020_08_20_205533_add_backup_state_column_to_backups.php @@ -8,10 +8,8 @@ class AddBackupStateColumnToBackups extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('backups', function (Blueprint $table) { $table->boolean('is_successful')->after('uuid')->default(true); @@ -20,10 +18,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('backups', function (Blueprint $table) { $table->dropColumn('is_successful'); diff --git a/database/migrations/2020_08_22_132500_update_bytes_to_unsigned_bigint.php b/database/migrations/2020_08_22_132500_update_bytes_to_unsigned_bigint.php index e8e9c38f37..5c4adca1a5 100644 --- a/database/migrations/2020_08_22_132500_update_bytes_to_unsigned_bigint.php +++ b/database/migrations/2020_08_22_132500_update_bytes_to_unsigned_bigint.php @@ -8,10 +8,8 @@ class UpdateBytesToUnsignedBigint extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('backups', function (Blueprint $table) { $table->unsignedBigInteger('bytes')->default(0)->change(); @@ -20,10 +18,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('backups', function (Blueprint $table) { $table->integer('bytes')->default(0)->change(); diff --git a/database/migrations/2020_08_23_175331_modify_checksums_column_for_backups.php b/database/migrations/2020_08_23_175331_modify_checksums_column_for_backups.php index 0de248bfd5..763b204571 100644 --- a/database/migrations/2020_08_23_175331_modify_checksums_column_for_backups.php +++ b/database/migrations/2020_08_23_175331_modify_checksums_column_for_backups.php @@ -9,10 +9,8 @@ class ModifyChecksumsColumnForBackups extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('backups', function (Blueprint $table) { $table->renameColumn('sha256_hash', 'checksum'); @@ -25,10 +23,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('backups', function (Blueprint $table) { $table->renameColumn('checksum', 'sha256_hash'); diff --git a/database/migrations/2020_09_13_110007_drop_packs_from_servers.php b/database/migrations/2020_09_13_110007_drop_packs_from_servers.php index 638435a81f..53cba54f50 100644 --- a/database/migrations/2020_09_13_110007_drop_packs_from_servers.php +++ b/database/migrations/2020_09_13_110007_drop_packs_from_servers.php @@ -8,10 +8,8 @@ class DropPacksFromServers extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('servers', function (Blueprint $table) { $table->dropForeign(['pack_id']); @@ -21,10 +19,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('servers', function (Blueprint $table) { $table->unsignedInteger('pack_id')->after('egg_id')->nullable(); diff --git a/database/migrations/2020_09_13_110021_drop_packs_from_api_key_permissions.php b/database/migrations/2020_09_13_110021_drop_packs_from_api_key_permissions.php index 9bcce8d4db..7c051db2cc 100644 --- a/database/migrations/2020_09_13_110021_drop_packs_from_api_key_permissions.php +++ b/database/migrations/2020_09_13_110021_drop_packs_from_api_key_permissions.php @@ -8,10 +8,8 @@ class DropPacksFromApiKeyPermissions extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('api_keys', function (Blueprint $table) { $table->dropColumn('r_packs'); @@ -20,10 +18,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('api_keys', function (Blueprint $table) { $table->unsignedTinyInteger('r_packs')->default(0); diff --git a/database/migrations/2020_09_13_110047_drop_packs_table.php b/database/migrations/2020_09_13_110047_drop_packs_table.php index 4f83c0f2e7..58194b8fa0 100644 --- a/database/migrations/2020_09_13_110047_drop_packs_table.php +++ b/database/migrations/2020_09_13_110047_drop_packs_table.php @@ -8,20 +8,16 @@ class DropPacksTable extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::dropIfExists('packs'); } /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::create('packs', function (Blueprint $table) { $table->increments('id'); diff --git a/database/migrations/2020_09_13_113503_drop_daemon_key_table.php b/database/migrations/2020_09_13_113503_drop_daemon_key_table.php index 7b90d41b9b..274f9fd979 100644 --- a/database/migrations/2020_09_13_113503_drop_daemon_key_table.php +++ b/database/migrations/2020_09_13_113503_drop_daemon_key_table.php @@ -8,20 +8,16 @@ class DropDaemonKeyTable extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::dropIfExists('daemon_keys'); } /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::create('daemon_keys', function (Blueprint $table) { $table->increments('id'); diff --git a/database/migrations/2020_10_10_165437_change_unique_database_name_to_account_for_server.php b/database/migrations/2020_10_10_165437_change_unique_database_name_to_account_for_server.php index 7420989a71..6a277e4495 100644 --- a/database/migrations/2020_10_10_165437_change_unique_database_name_to_account_for_server.php +++ b/database/migrations/2020_10_10_165437_change_unique_database_name_to_account_for_server.php @@ -8,10 +8,8 @@ class ChangeUniqueDatabaseNameToAccountForServer extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('databases', function (Blueprint $table) { $table->dropUnique(['database_host_id', 'database']); @@ -24,10 +22,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('databases', function (Blueprint $table) { $table->dropUnique(['database_host_id', 'server_id', 'database']); diff --git a/database/migrations/2020_10_26_194904_remove_nullable_from_schedule_name_field.php b/database/migrations/2020_10_26_194904_remove_nullable_from_schedule_name_field.php index 69593e656e..d0b3118e5a 100644 --- a/database/migrations/2020_10_26_194904_remove_nullable_from_schedule_name_field.php +++ b/database/migrations/2020_10_26_194904_remove_nullable_from_schedule_name_field.php @@ -9,10 +9,8 @@ class RemoveNullableFromScheduleNameField extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { DB::update("UPDATE schedules SET name = 'Schedule' WHERE name IS NULL OR name = ''"); @@ -23,10 +21,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('schedules', function (Blueprint $table) { $table->string('name')->nullable()->change(); diff --git a/database/migrations/2020_11_02_201014_add_features_column_to_eggs.php b/database/migrations/2020_11_02_201014_add_features_column_to_eggs.php index 1a001ae988..e134fe9a61 100644 --- a/database/migrations/2020_11_02_201014_add_features_column_to_eggs.php +++ b/database/migrations/2020_11_02_201014_add_features_column_to_eggs.php @@ -8,10 +8,8 @@ class AddFeaturesColumnToEggs extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('eggs', function (Blueprint $table) { $table->json('features')->after('description')->nullable(); @@ -20,10 +18,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('eggs', function (Blueprint $table) { $table->dropColumn('features'); diff --git a/database/migrations/2020_12_12_102435_support_multiple_docker_images_and_updates.php b/database/migrations/2020_12_12_102435_support_multiple_docker_images_and_updates.php index 776d3c6ba2..c7d2cff7f4 100644 --- a/database/migrations/2020_12_12_102435_support_multiple_docker_images_and_updates.php +++ b/database/migrations/2020_12_12_102435_support_multiple_docker_images_and_updates.php @@ -9,19 +9,22 @@ class SupportMultipleDockerImagesAndUpdates extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('eggs', function (Blueprint $table) { $table->json('docker_images')->after('docker_image')->nullable(); $table->text('update_url')->after('docker_images')->nullable(); }); - Schema::table('eggs', function (Blueprint $table) { - DB::statement('UPDATE `eggs` SET `docker_images` = JSON_ARRAY(docker_image)'); - }); + switch (DB::getPdo()->getAttribute(PDO::ATTR_DRIVER_NAME)) { + case 'mysql': + DB::table('eggs')->update(['docker_images' => DB::raw('JSON_ARRAY(docker_image)')]); + break; + case 'pgsql': + DB::table('eggs')->update(['docker_images' => DB::raw('jsonb_build_array(docker_image)')]); + break; + } Schema::table('eggs', function (Blueprint $table) { $table->dropColumn('docker_image'); @@ -30,18 +33,22 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('eggs', function (Blueprint $table) { $table->text('docker_image')->after('docker_images'); }); - Schema::table('eggs', function (Blueprint $table) { - DB::statement('UPDATE `eggs` SET `docker_image` = JSON_UNQUOTE(JSON_EXTRACT(docker_images, "$[0]"))'); - }); + switch (DB::getPdo()->getAttribute(PDO::ATTR_DRIVER_NAME)) { + case 'mysql': + DB::table('eggs')->update(['docker_images' => DB::raw('JSON_UNQUOTE(JSON_EXTRACT(docker_images, "$[0]")')]); + break; + case 'pgsql': + DB::table('eggs')->update(['docker_images' => DB::raw('JSON_UNQUOTE(JSON_EXTRACT(docker_images, "$[0]")')]); + DB::table('eggs')->update(['docker_images' => DB::raw('docker_images->>0')]); + break; + } Schema::table('eggs', function (Blueprint $table) { $table->dropColumn('docker_images'); diff --git a/database/migrations/2020_12_14_013707_make_successful_nullable_in_server_transfers.php b/database/migrations/2020_12_14_013707_make_successful_nullable_in_server_transfers.php index 0a28852844..4c28b6c2b1 100644 --- a/database/migrations/2020_12_14_013707_make_successful_nullable_in_server_transfers.php +++ b/database/migrations/2020_12_14_013707_make_successful_nullable_in_server_transfers.php @@ -8,10 +8,8 @@ class MakeSuccessfulNullableInServerTransfers extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('server_transfers', function (Blueprint $table) { $table->boolean('successful')->nullable()->default(null)->change(); @@ -20,10 +18,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('server_transfers', function (Blueprint $table) { $table->boolean('successful')->default(0)->change(); diff --git a/database/migrations/2020_12_17_014330_add_archived_field_to_server_transfers_table.php b/database/migrations/2020_12_17_014330_add_archived_field_to_server_transfers_table.php index 1162d8a4ff..bc5d3356d9 100644 --- a/database/migrations/2020_12_17_014330_add_archived_field_to_server_transfers_table.php +++ b/database/migrations/2020_12_17_014330_add_archived_field_to_server_transfers_table.php @@ -9,27 +9,21 @@ class AddArchivedFieldToServerTransfersTable extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('server_transfers', function (Blueprint $table) { $table->boolean('archived')->default(0)->after('new_additional_allocations'); }); // Update archived to all be true on existing transfers. - Schema::table('server_transfers', function (Blueprint $table) { - DB::statement('UPDATE `server_transfers` SET `archived` = 1 WHERE `successful` = 1'); - }); + DB::table('server_transfers')->where('successful', true)->update(['archived' => 1]); } /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('server_transfers', function (Blueprint $table) { $table->dropColumn('archived'); diff --git a/database/migrations/2020_12_24_092449_make_allocation_fields_json.php b/database/migrations/2020_12_24_092449_make_allocation_fields_json.php deleted file mode 100644 index bceec9de7d..0000000000 --- a/database/migrations/2020_12_24_092449_make_allocation_fields_json.php +++ /dev/null @@ -1,34 +0,0 @@ -json('old_additional_allocations')->nullable()->change(); - $table->json('new_additional_allocations')->nullable()->change(); - }); - } - - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - Schema::table('server_transfers', function (Blueprint $table) { - $table->string('old_additional_allocations')->nullable()->change(); - $table->string('new_additional_allocations')->nullable()->change(); - }); - } -} diff --git a/database/migrations/2020_12_26_184914_add_upload_id_column_to_backups_table.php b/database/migrations/2020_12_26_184914_add_upload_id_column_to_backups_table.php index 2e1c50556b..771e06ab11 100644 --- a/database/migrations/2020_12_26_184914_add_upload_id_column_to_backups_table.php +++ b/database/migrations/2020_12_26_184914_add_upload_id_column_to_backups_table.php @@ -8,10 +8,8 @@ class AddUploadIdColumnToBackupsTable extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('backups', function (Blueprint $table) { $table->text('upload_id')->nullable()->after('uuid'); @@ -20,10 +18,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('backups', function (Blueprint $table) { $table->dropColumn('upload_id'); diff --git a/database/migrations/2021_01_10_153937_add_file_denylist_to_egg_configs.php b/database/migrations/2021_01_10_153937_add_file_denylist_to_egg_configs.php index 8d617fc199..4a956625e3 100644 --- a/database/migrations/2021_01_10_153937_add_file_denylist_to_egg_configs.php +++ b/database/migrations/2021_01_10_153937_add_file_denylist_to_egg_configs.php @@ -8,10 +8,8 @@ class AddFileDenylistToEggConfigs extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('eggs', function (Blueprint $table) { $table->text('file_denylist')->after('docker_images'); @@ -20,10 +18,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('eggs', function (Blueprint $table) { $table->dropColumn('file_denylist'); diff --git a/database/migrations/2021_01_13_013420_add_cron_month.php b/database/migrations/2021_01_13_013420_add_cron_month.php index 85e534248b..da0bf88418 100644 --- a/database/migrations/2021_01_13_013420_add_cron_month.php +++ b/database/migrations/2021_01_13_013420_add_cron_month.php @@ -8,10 +8,8 @@ class AddCronMonth extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('schedules', function (Blueprint $table) { $table->string('cron_month')->after('cron_day_of_week'); @@ -20,10 +18,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('schedules', function (Blueprint $table) { $table->dropColumn('cron_month'); diff --git a/database/migrations/2021_01_17_102401_create_audit_logs_table.php b/database/migrations/2021_01_17_102401_create_audit_logs_table.php index f67e7d647c..d0fddf801e 100644 --- a/database/migrations/2021_01_17_102401_create_audit_logs_table.php +++ b/database/migrations/2021_01_17_102401_create_audit_logs_table.php @@ -8,10 +8,8 @@ class CreateAuditLogsTable extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::create('audit_logs', function (Blueprint $table) { $table->id(); @@ -32,10 +30,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::dropIfExists('audit_logs'); } diff --git a/database/migrations/2021_01_17_152623_add_generic_server_status_column.php b/database/migrations/2021_01_17_152623_add_generic_server_status_column.php index 12e6abb958..f8c9dec2ca 100644 --- a/database/migrations/2021_01_17_152623_add_generic_server_status_column.php +++ b/database/migrations/2021_01_17_152623_add_generic_server_status_column.php @@ -9,20 +9,16 @@ class AddGenericServerStatusColumn extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('servers', function (Blueprint $table) { $table->string('status')->nullable()->after('description'); }); - DB::transaction(function () { - DB::update('UPDATE servers SET `status` = \'suspended\' WHERE `suspended` = 1'); - DB::update('UPDATE servers SET `status` = \'installing\' WHERE `installed` = 0'); - DB::update('UPDATE servers SET `status` = \'install_failed\' WHERE `installed` = 2'); - }); + DB::table('servers')->where('suspended', 1)->update(['status' => 'suspended']); + DB::table('servers')->where('installed', 1)->update(['status' => 'installing']); + DB::table('servers')->where('installed', 1)->update(['status' => 'install_failed']); Schema::table('servers', function (Blueprint $table) { $table->dropColumn('suspended'); @@ -32,21 +28,17 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('servers', function (Blueprint $table) { $table->unsignedTinyInteger('suspended')->default(0); $table->unsignedTinyInteger('installed')->default(0); }); - DB::transaction(function () { - DB::update('UPDATE servers SET `suspended` = 1 WHERE `status` = \'suspended\''); - DB::update('UPDATE servers SET `installed` = 1 WHERE `status` IS NULL'); - DB::update('UPDATE servers SET `installed` = 2 WHERE `status` = \'install_failed\''); - }); + DB::table('servers')->where('status', 'suspended')->update(['suspended' => 1]); + DB::table('servers')->whereNull('status')->update(['installed' => 1]); + DB::table('servers')->where('status', 'install_failed')->update(['installed' => 2]); Schema::table('servers', function (Blueprint $table) { $table->dropColumn('status'); diff --git a/database/migrations/2021_01_26_210502_update_file_denylist_to_json.php b/database/migrations/2021_01_26_210502_update_file_denylist_to_json.php index af49611353..7c2c5fa069 100644 --- a/database/migrations/2021_01_26_210502_update_file_denylist_to_json.php +++ b/database/migrations/2021_01_26_210502_update_file_denylist_to_json.php @@ -8,10 +8,8 @@ class UpdateFileDenylistToJson extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('eggs', function (Blueprint $table) { $table->dropColumn('file_denylist'); @@ -24,10 +22,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('eggs', function (Blueprint $table) { $table->dropColumn('file_denylist'); diff --git a/database/migrations/2021_02_23_205021_add_index_for_server_and_action.php b/database/migrations/2021_02_23_205021_add_index_for_server_and_action.php index 888125468f..1fd63559c1 100644 --- a/database/migrations/2021_02_23_205021_add_index_for_server_and_action.php +++ b/database/migrations/2021_02_23_205021_add_index_for_server_and_action.php @@ -8,10 +8,8 @@ class AddIndexForServerAndAction extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('audit_logs', function (Blueprint $table) { // Doing the index in this order lets me use the action alone without the server @@ -27,10 +25,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('audit_logs', function (Blueprint $table) { $table->dropIndex(['action', 'server_id']); diff --git a/database/migrations/2021_02_23_212657_make_sftp_port_unsigned_int.php b/database/migrations/2021_02_23_212657_make_sftp_port_unsigned_int.php index 8eea84819e..b9196a0ae5 100644 --- a/database/migrations/2021_02_23_212657_make_sftp_port_unsigned_int.php +++ b/database/migrations/2021_02_23_212657_make_sftp_port_unsigned_int.php @@ -8,10 +8,8 @@ class MakeSftpPortUnsignedInt extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('nodes', function (Blueprint $table) { $table->unsignedSmallInteger('daemonSFTP')->default(2022)->change(); @@ -20,10 +18,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('nodes', function (Blueprint $table) { $table->smallInteger('daemonSFTP')->default(2022)->change(); diff --git a/database/migrations/2021_03_21_104718_force_cron_month_field_to_have_value_if_missing.php b/database/migrations/2021_03_21_104718_force_cron_month_field_to_have_value_if_missing.php index 57e1299523..61abdbd6c6 100644 --- a/database/migrations/2021_03_21_104718_force_cron_month_field_to_have_value_if_missing.php +++ b/database/migrations/2021_03_21_104718_force_cron_month_field_to_have_value_if_missing.php @@ -1,30 +1,22 @@ where('cron_month', '')->update(['cron_month' => '*']); } /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { // No down function. } 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 index 703f1524f9..0790454966 100644 --- 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 @@ -8,10 +8,8 @@ class AddContinueOnFailureOptionToTasks extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('tasks', function (Blueprint $table) { $table->unsignedTinyInteger('continue_on_failure')->after('is_queued')->default(0); @@ -20,10 +18,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { 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 index 91bb43be91..a0143fdd6f 100644 --- 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 @@ -8,10 +8,8 @@ class AddOnlyRunWhenServerOnlineOptionToSchedules extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('schedules', function (Blueprint $table) { $table->unsignedTinyInteger('only_when_online')->after('is_processing')->default(0); @@ -20,10 +18,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('schedules', function (Blueprint $table) { $table->dropColumn('only_when_online'); 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 index bafa4dd76d..3296e3e8fb 100644 --- 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 @@ -8,10 +8,8 @@ class AddSupportForLockingABackup extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('backups', function (Blueprint $table) { $table->unsignedTinyInteger('is_locked')->after('is_successful')->default(0); @@ -20,10 +18,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('backups', function (Blueprint $table) { $table->dropColumn('is_locked'); diff --git a/database/migrations/2021_07_12_013420_remove_userinteraction.php b/database/migrations/2021_07_12_013420_remove_userinteraction.php index 05321d4b33..33b4d5d3b4 100644 --- a/database/migrations/2021_07_12_013420_remove_userinteraction.php +++ b/database/migrations/2021_07_12_013420_remove_userinteraction.php @@ -7,22 +7,41 @@ class RemoveUserInteraction extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { // Remove User Interaction from startup config - DB::table('eggs')->update([ - 'config_startup' => DB::raw('JSON_REMOVE(config_startup, \'$.userInteraction\')'), - ]); + switch (DB::getPdo()->getAttribute(PDO::ATTR_DRIVER_NAME)) { + case 'mysql': + DB::table('eggs')->update([ + 'config_startup' => DB::raw('JSON_REMOVE(config_startup, \'$.userInteraction\')'), + ]); + break; + case 'pgsql': + DB::table('eggs')->update([ + 'config_startup' => DB::raw('config_startup::jsonb - \'userInteraction\''), + ]); + break; + } } - public function down() + /** + * Reverse the migrations. + */ + public function down(): void { // Add blank User Interaction array back to startup config - DB::table('eggs')->update([ - 'config_startup' => DB::raw('JSON_SET(config_startup, \'$.userInteraction\', JSON_ARRAY())'), - ]); + switch (DB::getPdo()->getAttribute(PDO::ATTR_DRIVER_NAME)) { + case 'mysql': + DB::table('eggs')->update([ + 'config_startup' => DB::raw('JSON_SET(config_startup, \'$.userInteraction\', JSON_ARRAY())'), + ]); + break; + case 'pgsql': + DB::table('eggs')->update([ + 'config_startup' => DB::raw('jsonb_set(config_startup::jsonb, \'$.userInteraction\', jsonb_build_array())'), + ]); + break; + } } } diff --git a/database/migrations/2021_07_17_211512_create_user_ssh_keys_table.php b/database/migrations/2021_07_17_211512_create_user_ssh_keys_table.php index d5b8a13c6a..a33bb4d310 100644 --- a/database/migrations/2021_07_17_211512_create_user_ssh_keys_table.php +++ b/database/migrations/2021_07_17_211512_create_user_ssh_keys_table.php @@ -9,7 +9,7 @@ class CreateUserSshKeysTable extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::create('user_ssh_keys', function (Blueprint $table) { $table->increments('id'); @@ -27,7 +27,7 @@ public function up() /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::dropIfExists('user_ssh_keys'); } diff --git a/database/migrations/2021_08_03_210600_change_successful_field_to_default_to_false_on_backups_table.php b/database/migrations/2021_08_03_210600_change_successful_field_to_default_to_false_on_backups_table.php index d47b0e5d2f..b9284eb0cc 100644 --- a/database/migrations/2021_08_03_210600_change_successful_field_to_default_to_false_on_backups_table.php +++ b/database/migrations/2021_08_03_210600_change_successful_field_to_default_to_false_on_backups_table.php @@ -9,10 +9,8 @@ class ChangeSuccessfulFieldToDefaultToFalseOnBackupsTable extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('backups', function (Blueprint $table) { $table->boolean('is_successful')->after('uuid')->default(false)->change(); @@ -26,10 +24,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('backups', function (Blueprint $table) { $table->boolean('is_successful')->after('uuid')->default(true)->change(); diff --git a/database/migrations/2021_08_21_175111_add_foreign_keys_to_mount_node_table.php b/database/migrations/2021_08_21_175111_add_foreign_keys_to_mount_node_table.php index fad8dc1936..5210f60d2b 100644 --- a/database/migrations/2021_08_21_175111_add_foreign_keys_to_mount_node_table.php +++ b/database/migrations/2021_08_21_175111_add_foreign_keys_to_mount_node_table.php @@ -9,10 +9,8 @@ class AddForeignKeysToMountNodeTable extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { // Fix the columns having a different type than their relations. Schema::table('mount_node', function (Blueprint $table) { @@ -46,10 +44,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('mount_node', function (Blueprint $table) { $table->dropForeign(['node_id']); diff --git a/database/migrations/2021_08_21_175118_add_foreign_keys_to_mount_server_table.php b/database/migrations/2021_08_21_175118_add_foreign_keys_to_mount_server_table.php index 9c5a403b20..f564ec491e 100644 --- a/database/migrations/2021_08_21_175118_add_foreign_keys_to_mount_server_table.php +++ b/database/migrations/2021_08_21_175118_add_foreign_keys_to_mount_server_table.php @@ -8,10 +8,8 @@ class AddForeignKeysToMountServerTable extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { // Fix the columns having a different type than their relations. Schema::table('mount_server', function (Blueprint $table) { @@ -45,10 +43,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('mount_server', function (Blueprint $table) { $table->dropForeign(['server_id']); diff --git a/database/migrations/2021_08_21_180921_add_foreign_keys_to_egg_mount_table.php b/database/migrations/2021_08_21_180921_add_foreign_keys_to_egg_mount_table.php index 7bf99506b7..11285a5c54 100644 --- a/database/migrations/2021_08_21_180921_add_foreign_keys_to_egg_mount_table.php +++ b/database/migrations/2021_08_21_180921_add_foreign_keys_to_egg_mount_table.php @@ -8,10 +8,8 @@ class AddForeignKeysToEggMountTable extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { // Fix the columns having a different type than their relations. Schema::table('egg_mount', function (Blueprint $table) { @@ -45,10 +43,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('egg_mount', function (Blueprint $table) { $table->dropForeign(['egg_id']); diff --git a/database/migrations/2022_01_25_030847_drop_google_analytics.php b/database/migrations/2022_01_25_030847_drop_google_analytics.php index 5daf0bc39e..0d65e5d4d9 100644 --- a/database/migrations/2022_01_25_030847_drop_google_analytics.php +++ b/database/migrations/2022_01_25_030847_drop_google_analytics.php @@ -7,25 +7,19 @@ class DropGoogleAnalytics extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { DB::table('settings')->where('key', 'settings::app:analytics')->delete(); } /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { - DB::table('settings')->insert( - [ + DB::table('settings')->insert([ 'key' => 'settings::app:analytics', - ] - ); + ]); } } diff --git a/database/migrations/2022_05_07_165334_migrate_egg_images_array_to_new_format.php b/database/migrations/2022_05_07_165334_migrate_egg_images_array_to_new_format.php index 78dfe6e37a..057ce9d743 100644 --- a/database/migrations/2022_05_07_165334_migrate_egg_images_array_to_new_format.php +++ b/database/migrations/2022_05_07_165334_migrate_egg_images_array_to_new_format.php @@ -10,7 +10,7 @@ class MigrateEggImagesArrayToNewFormat extends Migration * images array to both exist, and have key => value pairings to support naming the * images provided. */ - public function up() + public function up(): void { DB::table('eggs')->select(['id', 'docker_images'])->cursor()->each(function ($egg) { $images = is_null($egg->docker_images) ? [] : json_decode($egg->docker_images, true, 512, JSON_THROW_ON_ERROR); @@ -26,10 +26,8 @@ public function up() /** * Reverse the migrations. This just keeps the values from the docker images array. - * - * @return void */ - public function down() + public function down(): void { DB::table('eggs')->select(['id', 'docker_images'])->cursor()->each(function ($egg) { DB::table('eggs')->where('id', $egg->id)->update([ diff --git a/database/migrations/2022_05_28_135717_create_activity_logs_table.php b/database/migrations/2022_05_28_135717_create_activity_logs_table.php index 448439dc81..4c4f05f1f1 100644 --- a/database/migrations/2022_05_28_135717_create_activity_logs_table.php +++ b/database/migrations/2022_05_28_135717_create_activity_logs_table.php @@ -8,10 +8,8 @@ class CreateActivityLogsTable extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::create('activity_logs', function (Blueprint $table) { $table->id(); @@ -27,10 +25,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::dropIfExists('activity_logs'); } diff --git a/database/migrations/2022_05_29_140349_create_activity_log_actors_table.php b/database/migrations/2022_05_29_140349_create_activity_log_actors_table.php index 6dc45d7f8a..c9997ac419 100644 --- a/database/migrations/2022_05_29_140349_create_activity_log_actors_table.php +++ b/database/migrations/2022_05_29_140349_create_activity_log_actors_table.php @@ -8,10 +8,8 @@ class CreateActivityLogActorsTable extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::create('activity_log_subjects', function (Blueprint $table) { $table->id(); @@ -22,10 +20,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::dropIfExists('activity_log_subjects'); } diff --git a/database/migrations/2022_06_18_112822_track_api_key_usage_for_activity_events.php b/database/migrations/2022_06_18_112822_track_api_key_usage_for_activity_events.php index 6e35df9a28..f138786373 100644 --- a/database/migrations/2022_06_18_112822_track_api_key_usage_for_activity_events.php +++ b/database/migrations/2022_06_18_112822_track_api_key_usage_for_activity_events.php @@ -7,10 +7,8 @@ return new class () extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('activity_logs', function (Blueprint $table) { $table->unsignedInteger('api_key_id')->nullable()->after('actor_id'); @@ -19,10 +17,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('activity_logs', function (Blueprint $table) { $table->dropColumn('api_key_id'); diff --git a/database/migrations/2022_08_16_214400_add_force_outgoing_ip_column_to_eggs_table.php b/database/migrations/2022_08_16_214400_add_force_outgoing_ip_column_to_eggs_table.php index eb3a56bf26..c5b30a49ae 100644 --- a/database/migrations/2022_08_16_214400_add_force_outgoing_ip_column_to_eggs_table.php +++ b/database/migrations/2022_08_16_214400_add_force_outgoing_ip_column_to_eggs_table.php @@ -8,10 +8,8 @@ class AddForceOutgoingIpColumnToEggsTable extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('eggs', function (Blueprint $table) { $table->boolean('force_outgoing_ip')->default(false); @@ -20,10 +18,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('eggs', function (Blueprint $table) { $table->dropColumn('force_outgoing_ip'); diff --git a/database/migrations/2022_08_16_230204_add_installed_at_column_to_servers_table.php b/database/migrations/2022_08_16_230204_add_installed_at_column_to_servers_table.php index 50a5e23f84..541117a3aa 100644 --- a/database/migrations/2022_08_16_230204_add_installed_at_column_to_servers_table.php +++ b/database/migrations/2022_08_16_230204_add_installed_at_column_to_servers_table.php @@ -8,10 +8,8 @@ class AddInstalledAtColumnToServersTable extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('servers', function (Blueprint $table) { $table->timestamp('installed_at')->nullable(); @@ -20,10 +18,8 @@ public function up() /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('servers', function (Blueprint $table) { $table->dropColumn('installed_at'); diff --git a/database/migrations/2022_11_01_163744_make_ignored_files_column_nullable_on_backups_table.php b/database/migrations/2022_11_01_163744_make_ignored_files_column_nullable_on_backups_table.php new file mode 100644 index 0000000000..ed36c64685 --- /dev/null +++ b/database/migrations/2022_11_01_163744_make_ignored_files_column_nullable_on_backups_table.php @@ -0,0 +1,27 @@ +text('ignored_files')->nullable()->change(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('backups', function (Blueprint $table) { + $table->text('ignored_files')->change(); + }); + } +}; diff --git a/database/migrations/2022_11_01_165830_fix_language_column_type_on_users_table.php b/database/migrations/2022_11_01_165830_fix_language_column_type_on_users_table.php new file mode 100644 index 0000000000..0268810fd5 --- /dev/null +++ b/database/migrations/2022_11_01_165830_fix_language_column_type_on_users_table.php @@ -0,0 +1,36 @@ +getAttribute(PDO::ATTR_DRIVER_NAME)) { + case 'mysql': + DB::statement('ALTER TABLE users MODIFY COLUMN language VARCHAR(5)'); + break; + case 'pgsql': + DB::statement('ALTER TABLE users ALTER COLUMN language TYPE varchar(5)'); + break; + } + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + switch (DB::getPdo()->getAttribute(PDO::ATTR_DRIVER_NAME)) { + case 'mysql': + DB::statement('ALTER TABLE users MODIFY COLUMN language CHAR(5)'); + break; + case 'pgsql': + DB::statement('ALTER TABLE users ALTER COLUMN language TYPE CHAR(5)'); + break; + } + } +}; diff --git a/phpunit.xml b/phpunit.xml index efc42d687c..5319118fd4 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -24,8 +24,8 @@ + - diff --git a/tests/Integration/Api/Application/Location/LocationControllerTest.php b/tests/Integration/Api/Application/Location/LocationControllerTest.php index ed75413ac0..7a02092b19 100644 --- a/tests/Integration/Api/Application/Location/LocationControllerTest.php +++ b/tests/Integration/Api/Application/Location/LocationControllerTest.php @@ -249,7 +249,7 @@ public function testKeyWithoutPermissionCannotLoadRelationship() */ public function testGetMissingLocation() { - $response = $this->getJson('/api/application/locations/nil'); + $response = $this->getJson('/api/application/locations/0'); $this->assertNotFoundJson($response); } diff --git a/tests/Integration/Api/Application/Nests/EggControllerTest.php b/tests/Integration/Api/Application/Nests/EggControllerTest.php index 07a527e0f0..e41b20eb43 100644 --- a/tests/Integration/Api/Application/Nests/EggControllerTest.php +++ b/tests/Integration/Api/Application/Nests/EggControllerTest.php @@ -104,7 +104,7 @@ public function testGetMissingEgg() { $egg = Egg::query()->findOrFail(1); - $response = $this->getJson('/api/application/nests/' . $egg->nest_id . '/eggs/nil'); + $response = $this->getJson('/api/application/nests/' . $egg->nest_id . '/eggs/0'); $this->assertNotFoundJson($response); } diff --git a/tests/Integration/Api/Application/Nests/NestControllerTest.php b/tests/Integration/Api/Application/Nests/NestControllerTest.php index 799fc18ac7..5cbed783c2 100644 --- a/tests/Integration/Api/Application/Nests/NestControllerTest.php +++ b/tests/Integration/Api/Application/Nests/NestControllerTest.php @@ -108,7 +108,7 @@ public function testSingleNestWithEggsIncluded() */ public function testGetMissingNest() { - $response = $this->getJson('/api/application/nests/nil'); + $response = $this->getJson('/api/application/nests/0'); $this->assertNotFoundJson($response); } diff --git a/tests/Integration/Api/Application/Users/ExternalUserControllerTest.php b/tests/Integration/Api/Application/Users/ExternalUserControllerTest.php index fc37b72329..1c02df7fca 100644 --- a/tests/Integration/Api/Application/Users/ExternalUserControllerTest.php +++ b/tests/Integration/Api/Application/Users/ExternalUserControllerTest.php @@ -51,7 +51,7 @@ public function testGetRemoteUser() */ public function testGetMissingUser() { - $response = $this->getJson('/api/application/users/external/nil'); + $response = $this->getJson('/api/application/users/external/0'); $this->assertNotFoundJson($response); } diff --git a/tests/Integration/Api/Application/Users/UserControllerTest.php b/tests/Integration/Api/Application/Users/UserControllerTest.php index 713f6264ad..0aebe3d9b5 100644 --- a/tests/Integration/Api/Application/Users/UserControllerTest.php +++ b/tests/Integration/Api/Application/Users/UserControllerTest.php @@ -184,7 +184,7 @@ public function testKeyWithoutPermissionCannotLoadRelationship() */ public function testGetMissingUser() { - $response = $this->getJson('/api/application/users/nil'); + $response = $this->getJson('/api/application/users/0'); $this->assertNotFoundJson($response); } diff --git a/tests/Integration/Api/Client/ClientControllerTest.php b/tests/Integration/Api/Client/ClientControllerTest.php index 033b893c31..ed0cada005 100644 --- a/tests/Integration/Api/Client/ClientControllerTest.php +++ b/tests/Integration/Api/Client/ClientControllerTest.php @@ -18,10 +18,10 @@ class ClientControllerTest extends ClientApiIntegrationTestCase */ public function testOnlyLoggedInUsersServersAreReturned() { - /** @var \Pterodactyl\Models\User[] $users */ + /** @var User[] $users */ $users = User::factory()->times(3)->create(); - /** @var \Pterodactyl\Models\Server[] $servers */ + /** @var Server[] $servers */ $servers = [ $this->createServerModel(['user_id' => $users[0]->id]), $this->createServerModel(['user_id' => $users[1]->id]), @@ -45,11 +45,11 @@ public function testOnlyLoggedInUsersServersAreReturned() */ public function testServersAreFilteredUsingNameAndUuidInformation() { - /** @var \Pterodactyl\Models\User[] $users */ + /** @var User[] $users */ $users = User::factory()->times(2)->create(); $users[0]->update(['root_admin' => true]); - /** @var \Pterodactyl\Models\Server[] $servers */ + /** @var Server[] $servers */ $servers = [ $this->createServerModel(['user_id' => $users[0]->id, 'name' => 'Julia']), $this->createServerModel(['user_id' => $users[1]->id, 'uuidShort' => '12121212', 'name' => 'Janice']), @@ -101,8 +101,8 @@ public function testServersAreFilteredUsingNameAndUuidInformation() */ public function testServersAreFilteredUsingAllocationInformation() { - /** @var \Pterodactyl\Models\User $user */ - /** @var \Pterodactyl\Models\Server $server */ + /** @var User $user */ + /** @var Server $server */ [$user, $server] = $this->generateTestAccount(); $server2 = $this->createServerModel(['user_id' => $user->id, 'node_id' => $server->node_id]); @@ -143,7 +143,7 @@ public function testServersAreFilteredUsingAllocationInformation() */ public function testServersUserIsASubuserOfAreReturned() { - /** @var \Pterodactyl\Models\User[] $users */ + /** @var User[] $users */ $users = User::factory()->times(3)->create(); $servers = [ $this->createServerModel(['user_id' => $users[0]->id]), @@ -174,7 +174,7 @@ public function testServersUserIsASubuserOfAreReturned() */ public function testFilterOnlyOwnerServers() { - /** @var \Pterodactyl\Models\User[] $users */ + /** @var User[] $users */ $users = User::factory()->times(3)->create(); $servers = [ $this->createServerModel(['user_id' => $users[0]->id]), @@ -203,7 +203,7 @@ public function testFilterOnlyOwnerServers() */ public function testPermissionsAreReturned() { - /** @var \Pterodactyl\Models\User $user */ + /** @var User $user */ $user = User::factory()->create(); $this->actingAs($user) @@ -223,7 +223,7 @@ public function testPermissionsAreReturned() */ public function testOnlyAdminLevelServersAreReturned() { - /** @var \Pterodactyl\Models\User[] $users */ + /** @var User[] $users */ $users = User::factory()->times(4)->create(); $users[0]->update(['root_admin' => true]); @@ -258,7 +258,7 @@ public function testOnlyAdminLevelServersAreReturned() */ public function testAllServersAreReturnedToAdmin() { - /** @var \Pterodactyl\Models\User[] $users */ + /** @var User[] $users */ $users = User::factory()->times(4)->create(); $users[0]->update(['root_admin' => true]); @@ -290,7 +290,7 @@ public function testAllServersAreReturnedToAdmin() */ public function testNoServersAreReturnedIfAdminFilterIsPassedByRegularUser(string $type) { - /** @var \Pterodactyl\Models\User[] $users */ + /** @var User[] $users */ $users = User::factory()->times(3)->create(); $this->createServerModel(['user_id' => $users[0]->id]); @@ -309,7 +309,7 @@ public function testNoServersAreReturnedIfAdminFilterIsPassedByRegularUser(strin */ public function testOnlyPrimaryAllocationIsReturnedToSubuser() { - /** @var \Pterodactyl\Models\Server $server */ + /** @var Server $server */ [$user, $server] = $this->generateTestAccount([Permission::ACTION_WEBSOCKET_CONNECT]); $server->allocation->notes = 'Test notes'; $server->allocation->save(); diff --git a/tests/Integration/Api/Client/Server/Startup/GetStartupAndVariablesTest.php b/tests/Integration/Api/Client/Server/Startup/GetStartupAndVariablesTest.php index a8a5b88e03..3024ecf86f 100644 --- a/tests/Integration/Api/Client/Server/Startup/GetStartupAndVariablesTest.php +++ b/tests/Integration/Api/Client/Server/Startup/GetStartupAndVariablesTest.php @@ -42,7 +42,7 @@ public function testStartupVariablesAreReturnedForServer(array $permissions) $response->assertJsonPath('object', 'list'); $response->assertJsonCount(1, 'data'); $response->assertJsonPath('data.0.object', EggVariable::RESOURCE_NAME); - $this->assertJsonTransformedWith($response->json('data.0.attributes'), $egg->variables[1]); + $this->assertJsonTransformedWith($response->json('data.0.attributes'), $egg->variables()->orderBy('id', 'desc')->first()); } /** diff --git a/tests/Integration/IntegrationTestCase.php b/tests/Integration/IntegrationTestCase.php index 103c97b9be..6672960d32 100644 --- a/tests/Integration/IntegrationTestCase.php +++ b/tests/Integration/IntegrationTestCase.php @@ -16,7 +16,7 @@ abstract class IntegrationTestCase extends TestCase use CreatesTestModels; use AssertsActivityLogged; - protected array $connectionsToTransact = ['mysql']; +// protected array $connectionsToTransact = ['pgsql']; protected $defaultHeaders = [ 'Accept' => 'application/json', diff --git a/tests/Integration/Services/Servers/ServerCreationServiceTest.php b/tests/Integration/Services/Servers/ServerCreationServiceTest.php index 92a873a189..aef7e8c1e7 100644 --- a/tests/Integration/Services/Servers/ServerCreationServiceTest.php +++ b/tests/Integration/Services/Servers/ServerCreationServiceTest.php @@ -127,9 +127,10 @@ public function testServerIsCreatedWithDeploymentObject() $this->assertNotNull($response->uuid); $this->assertSame($response->uuidShort, substr($response->uuid, 0, 8)); $this->assertSame($egg->id, $response->egg_id); - $this->assertCount(2, $response->variables); - $this->assertSame('123', $response->variables[0]->server_value); - $this->assertSame('server2.jar', $response->variables[1]->server_value); + $variables = $response->variables->sortBy('server_value')->values(); + $this->assertCount(2, $variables); + $this->assertSame('123', $variables->get(0)->server_value); + $this->assertSame('server2.jar', $variables->get(1)->server_value); foreach ($data as $key => $value) { if (in_array($key, ['allocation_additional', 'environment', 'start_on_completion'])) { diff --git a/tests/Integration/Services/Servers/StartupModificationServiceTest.php b/tests/Integration/Services/Servers/StartupModificationServiceTest.php index 47f4595f0e..9106b4740c 100644 --- a/tests/Integration/Services/Servers/StartupModificationServiceTest.php +++ b/tests/Integration/Services/Servers/StartupModificationServiceTest.php @@ -109,7 +109,7 @@ public function testEnvironmentVariablesCanBeUpdatedByAdmin() $clone = $this->cloneEggAndVariables($server->egg); // This makes the BUNGEE_VERSION variable not user editable. - $clone->variables()->first()->update([ + $clone->variables()->orderBy('id')->first()->update([ 'user_editable' => false, ]); @@ -118,7 +118,7 @@ public function testEnvironmentVariablesCanBeUpdatedByAdmin() ServerVariable::query()->updateOrCreate([ 'server_id' => $server->id, - 'variable_id' => $server->variables[0]->id, + 'variable_id' => $server->variables()->orderBy('id')->first()->id, ], ['variable_value' => 'EXIST']); $response = $this->getService()->handle($server, [ @@ -128,9 +128,10 @@ public function testEnvironmentVariablesCanBeUpdatedByAdmin() ], ]); - $this->assertCount(2, $response->variables); - $this->assertSame('EXIST', $response->variables[0]->server_value); - $this->assertSame('test.jar', $response->variables[1]->server_value); + $variables = $response->variables->sortBy('server_value')->values(); + $this->assertCount(2, $variables); + $this->assertSame('EXIST', $variables->get(0)->server_value); + $this->assertSame('test.jar', $variables->get(1)->server_value); $response = $this->getService() ->setUserLevel(User::USER_LEVEL_ADMIN) @@ -141,9 +142,11 @@ public function testEnvironmentVariablesCanBeUpdatedByAdmin() ], ]); - $this->assertCount(2, $response->variables); - $this->assertSame('1234', $response->variables[0]->server_value); - $this->assertSame('test.jar', $response->variables[1]->server_value); + $variables = $response->variables->sortBy('server_value')->values(); + + $this->assertCount(2, $variables); + $this->assertSame('1234', $variables->get(0)->server_value); + $this->assertSame('test.jar', $variables->get(1)->server_value); } /** diff --git a/tests/Integration/Services/Servers/VariableValidatorServiceTest.php b/tests/Integration/Services/Servers/VariableValidatorServiceTest.php index 7f0e157fa1..36cfb05d61 100644 --- a/tests/Integration/Services/Servers/VariableValidatorServiceTest.php +++ b/tests/Integration/Services/Servers/VariableValidatorServiceTest.php @@ -108,11 +108,12 @@ public function testEnvironmentVariablesCanBeUpdatedAsAdmin() ]); $this->assertInstanceOf(Collection::class, $response); - $this->assertCount(2, $response); - $this->assertSame('BUNGEE_VERSION', $response->get(0)->key); - $this->assertSame('123', $response->get(0)->value); - $this->assertSame('SERVER_JARFILE', $response->get(1)->key); - $this->assertSame('server.jar', $response->get(1)->value); + $variables = $response->sortBy('key')->values(); + $this->assertCount(2, $variables); + $this->assertSame('BUNGEE_VERSION', $variables->get(0)->key); + $this->assertSame('123', $variables->get(0)->value); + $this->assertSame('SERVER_JARFILE', $variables->get(1)->key); + $this->assertSame('server.jar', $variables->get(1)->value); } public function testNullableEnvironmentVariablesCanBeUsedCorrectly() diff --git a/tests/Traits/MocksPdoConnection.php b/tests/Traits/MocksPdoConnection.php deleted file mode 100644 index a938964638..0000000000 --- a/tests/Traits/MocksPdoConnection.php +++ /dev/null @@ -1,48 +0,0 @@ - $connection]); - $resolver->setDefaultConnection('mocked'); - - Model::setConnectionResolver($resolver); - - return $mock; - } - - /** - * Resets the mock state. - */ - protected function tearDownPdoMock(): void - { - if (!self::$initialResolver) { - return; - } - - Model::setConnectionResolver(self::$initialResolver); - - self::$initialResolver = null; - } -} From c475d601f30b6a591b740e0411c03514f4014753 Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Sun, 27 Nov 2022 11:53:46 -0700 Subject: [PATCH 285/458] ui: remove unused codemirror 5 editor --- package.json | 2 - .../components/elements/CodemirrorEditor.tsx | 219 ------------------ yarn.lock | 24 -- 3 files changed, 245 deletions(-) delete mode 100644 resources/scripts/components/elements/CodemirrorEditor.tsx diff --git a/package.json b/package.json index ef5fb95d61..68314a3707 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,6 @@ "boring-avatars": "1.7.0", "chart.js": "3.9.1", "classnames": "2.3.2", - "codemirror": "5.57.0", "copy-to-clipboard": "3.3.2", "date-fns": "2.29.3", "debounce": "1.2.1", @@ -89,7 +88,6 @@ "@testing-library/dom": "8.19.0", "@testing-library/react": "13.4.0", "@testing-library/user-event": "14.4.3", - "@types/codemirror": "0.0.109", "@types/debounce": "1.2.1", "@types/events": "3.0.0", "@types/node": "18.11.9", diff --git a/resources/scripts/components/elements/CodemirrorEditor.tsx b/resources/scripts/components/elements/CodemirrorEditor.tsx deleted file mode 100644 index 48948ad74b..0000000000 --- a/resources/scripts/components/elements/CodemirrorEditor.tsx +++ /dev/null @@ -1,219 +0,0 @@ -import CodeMirror from 'codemirror'; -import type { CSSProperties } from 'react'; -import { useCallback, useEffect, useState } from 'react'; -import styled from 'styled-components'; -import tw from 'twin.macro'; - -import modes from '@/modes'; - -require('codemirror/lib/codemirror.css'); -require('codemirror/theme/ayu-mirage.css'); -require('codemirror/addon/edit/closebrackets'); -require('codemirror/addon/edit/closetag'); -require('codemirror/addon/edit/matchbrackets'); -require('codemirror/addon/edit/matchtags'); -require('codemirror/addon/edit/trailingspace'); -require('codemirror/addon/fold/foldcode'); -require('codemirror/addon/fold/foldgutter.css'); -require('codemirror/addon/fold/foldgutter'); -require('codemirror/addon/fold/brace-fold'); -require('codemirror/addon/fold/comment-fold'); -require('codemirror/addon/fold/indent-fold'); -require('codemirror/addon/fold/markdown-fold'); -require('codemirror/addon/fold/xml-fold'); -require('codemirror/addon/hint/css-hint'); -require('codemirror/addon/hint/html-hint'); -require('codemirror/addon/hint/javascript-hint'); -require('codemirror/addon/hint/show-hint.css'); -require('codemirror/addon/hint/show-hint'); -require('codemirror/addon/hint/sql-hint'); -require('codemirror/addon/hint/xml-hint'); -require('codemirror/addon/mode/simple'); -require('codemirror/addon/dialog/dialog.css'); -require('codemirror/addon/dialog/dialog'); -require('codemirror/addon/scroll/annotatescrollbar'); -require('codemirror/addon/scroll/scrollpastend'); -require('codemirror/addon/scroll/simplescrollbars.css'); -require('codemirror/addon/scroll/simplescrollbars'); -require('codemirror/addon/search/jump-to-line'); -require('codemirror/addon/search/match-highlighter'); -require('codemirror/addon/search/matchesonscrollbar.css'); -require('codemirror/addon/search/matchesonscrollbar'); -require('codemirror/addon/search/search'); -require('codemirror/addon/search/searchcursor'); - -require('codemirror/mode/brainfuck/brainfuck'); -require('codemirror/mode/clike/clike'); -require('codemirror/mode/css/css'); -require('codemirror/mode/dart/dart'); -require('codemirror/mode/diff/diff'); -require('codemirror/mode/dockerfile/dockerfile'); -require('codemirror/mode/erlang/erlang'); -require('codemirror/mode/gfm/gfm'); -require('codemirror/mode/go/go'); -require('codemirror/mode/handlebars/handlebars'); -require('codemirror/mode/htmlembedded/htmlembedded'); -require('codemirror/mode/htmlmixed/htmlmixed'); -require('codemirror/mode/http/http'); -require('codemirror/mode/javascript/javascript'); -require('codemirror/mode/jsx/jsx'); -require('codemirror/mode/julia/julia'); -require('codemirror/mode/lua/lua'); -require('codemirror/mode/markdown/markdown'); -require('codemirror/mode/nginx/nginx'); -require('codemirror/mode/perl/perl'); -require('codemirror/mode/php/php'); -require('codemirror/mode/properties/properties'); -require('codemirror/mode/protobuf/protobuf'); -require('codemirror/mode/pug/pug'); -require('codemirror/mode/python/python'); -require('codemirror/mode/rpm/rpm'); -require('codemirror/mode/ruby/ruby'); -require('codemirror/mode/rust/rust'); -require('codemirror/mode/sass/sass'); -require('codemirror/mode/shell/shell'); -require('codemirror/mode/smarty/smarty'); -require('codemirror/mode/sql/sql'); -require('codemirror/mode/swift/swift'); -require('codemirror/mode/toml/toml'); -require('codemirror/mode/twig/twig'); -require('codemirror/mode/vue/vue'); -require('codemirror/mode/xml/xml'); -require('codemirror/mode/yaml/yaml'); - -const EditorContainer = styled.div` - min-height: 16rem; - height: calc(100vh - 20rem); - ${tw`relative`}; - - > div { - ${tw`rounded h-full`}; - } - - .CodeMirror { - font-size: 12px; - line-height: 1.375rem; - } - - .CodeMirror-linenumber { - padding: 1px 12px 0 12px !important; - } - - .CodeMirror-foldmarker { - color: #cbccc6; - text-shadow: none; - margin-left: 0.25rem; - margin-right: 0.25rem; - } -`; - -export interface Props { - style?: CSSProperties; - initialContent?: string; - mode: string; - filename?: string; - onModeChanged: (mode: string) => void; - fetchContent: (callback: () => Promise) => void; - onContentSaved: () => void; -} - -const findModeByFilename = (filename: string) => { - for (let i = 0; i < modes.length; i++) { - const info = modes[i]; - - if (info?.file !== undefined && info.file.test(filename)) { - return info; - } - } - - const dot = filename.lastIndexOf('.'); - const ext = dot > -1 && filename.substring(dot + 1, filename.length); - - if (ext) { - for (let i = 0; i < modes.length; i++) { - const info = modes[i]; - if (info?.ext !== undefined) { - for (let j = 0; j < info.ext.length; j++) { - if (info.ext[j] === ext) { - return info; - } - } - } - } - } - - return undefined; -}; - -export default ({ style, initialContent, filename, mode, fetchContent, onContentSaved, onModeChanged }: Props) => { - const [editor, setEditor] = useState(); - - const ref = useCallback<(_?: unknown) => void>(node => { - if (node === undefined) { - return; - } - - const e = CodeMirror.fromTextArea(node as HTMLTextAreaElement, { - mode: 'text/plain', - theme: 'ayu-mirage', - indentUnit: 4, - smartIndent: true, - tabSize: 4, - indentWithTabs: false, - lineWrapping: true, - lineNumbers: true, - fixedGutter: true, - scrollbarStyle: 'overlay', - coverGutterNextToScrollbar: false, - readOnly: false, - showCursorWhenSelecting: false, - autofocus: false, - spellcheck: true, - autocorrect: false, - autocapitalize: false, - lint: false, - // @ts-expect-error this property is actually used, the d.ts file for CodeMirror is incorrect. - autoCloseBrackets: true, - matchBrackets: true, - gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'], - }); - - setEditor(e); - }, []); - - useEffect(() => { - if (filename === undefined) { - return; - } - - onModeChanged(findModeByFilename(filename)?.mime || 'text/plain'); - }, [filename]); - - useEffect(() => { - editor && editor.setOption('mode', mode); - }, [editor, mode]); - - useEffect(() => { - editor && editor.setValue(initialContent || ''); - }, [editor, initialContent]); - - useEffect(() => { - if (!editor) { - fetchContent(() => Promise.reject(new Error('no editor session has been configured'))); - return; - } - - editor.addKeyMap({ - 'Ctrl-S': () => onContentSaved(), - 'Cmd-S': () => onContentSaved(), - }); - - fetchContent(() => Promise.resolve(editor.getValue())); - }, [editor, fetchContent, onContentSaved]); - - return ( - - "; - support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; -} )(); -var documentElement = document.documentElement; - - - -var - rkeyEvent = /^key/, - rmouseEvent = /^(?:mouse|pointer|contextmenu|drag|drop)|click/, - rtypenamespace = /^([^.]*)(?:\.(.+)|)/; - -function returnTrue() { - return true; -} - -function returnFalse() { - return false; -} - -// Support: IE <=9 only -// See #13393 for more info -function safeActiveElement() { - try { - return document.activeElement; - } catch ( err ) { } -} - -function on( elem, types, selector, data, fn, one ) { - var origFn, type; - - // Types can be a map of types/handlers - if ( typeof types === "object" ) { - - // ( types-Object, selector, data ) - if ( typeof selector !== "string" ) { - - // ( types-Object, data ) - data = data || selector; - selector = undefined; - } - for ( type in types ) { - on( elem, type, selector, data, types[ type ], one ); - } - return elem; - } - - if ( data == null && fn == null ) { - - // ( types, fn ) - fn = selector; - data = selector = undefined; - } else if ( fn == null ) { - if ( typeof selector === "string" ) { - - // ( types, selector, fn ) - fn = data; - data = undefined; - } else { - - // ( types, data, fn ) - fn = data; - data = selector; - selector = undefined; - } - } - if ( fn === false ) { - fn = returnFalse; - } else if ( !fn ) { - return elem; - } - - if ( one === 1 ) { - origFn = fn; - fn = function( event ) { - - // Can use an empty set, since event contains the info - jQuery().off( event ); - return origFn.apply( this, arguments ); - }; - - // Use same guid so caller can remove using origFn - fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); - } - return elem.each( function() { - jQuery.event.add( this, types, fn, data, selector ); - } ); -} - -/* - * Helper functions for managing events -- not part of the public interface. - * Props to Dean Edwards' addEvent library for many of the ideas. - */ -jQuery.event = { - - global: {}, - - add: function( elem, types, handler, data, selector ) { - - var handleObjIn, eventHandle, tmp, - events, t, handleObj, - special, handlers, type, namespaces, origType, - elemData = dataPriv.get( elem ); - - // Don't attach events to noData or text/comment nodes (but allow plain objects) - if ( !elemData ) { - return; - } - - // Caller can pass in an object of custom data in lieu of the handler - if ( handler.handler ) { - handleObjIn = handler; - handler = handleObjIn.handler; - selector = handleObjIn.selector; - } - - // Ensure that invalid selectors throw exceptions at attach time - // Evaluate against documentElement in case elem is a non-element node (e.g., document) - if ( selector ) { - jQuery.find.matchesSelector( documentElement, selector ); - } - - // Make sure that the handler has a unique ID, used to find/remove it later - if ( !handler.guid ) { - handler.guid = jQuery.guid++; - } - - // Init the element's event structure and main handler, if this is the first - if ( !( events = elemData.events ) ) { - events = elemData.events = {}; - } - if ( !( eventHandle = elemData.handle ) ) { - eventHandle = elemData.handle = function( e ) { - - // Discard the second event of a jQuery.event.trigger() and - // when an event is called after a page has unloaded - return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ? - jQuery.event.dispatch.apply( elem, arguments ) : undefined; - }; - } - - // Handle multiple events separated by a space - types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; - t = types.length; - while ( t-- ) { - tmp = rtypenamespace.exec( types[ t ] ) || []; - type = origType = tmp[ 1 ]; - namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); - - // There *must* be a type, no attaching namespace-only handlers - if ( !type ) { - continue; - } - - // If event changes its type, use the special event handlers for the changed type - special = jQuery.event.special[ type ] || {}; - - // If selector defined, determine special event api type, otherwise given type - type = ( selector ? special.delegateType : special.bindType ) || type; - - // Update special based on newly reset type - special = jQuery.event.special[ type ] || {}; - - // handleObj is passed to all event handlers - handleObj = jQuery.extend( { - type: type, - origType: origType, - data: data, - handler: handler, - guid: handler.guid, - selector: selector, - needsContext: selector && jQuery.expr.match.needsContext.test( selector ), - namespace: namespaces.join( "." ) - }, handleObjIn ); - - // Init the event handler queue if we're the first - if ( !( handlers = events[ type ] ) ) { - handlers = events[ type ] = []; - handlers.delegateCount = 0; - - // Only use addEventListener if the special events handler returns false - if ( !special.setup || - special.setup.call( elem, data, namespaces, eventHandle ) === false ) { - - if ( elem.addEventListener ) { - elem.addEventListener( type, eventHandle ); - } - } - } - - if ( special.add ) { - special.add.call( elem, handleObj ); - - if ( !handleObj.handler.guid ) { - handleObj.handler.guid = handler.guid; - } - } - - // Add to the element's handler list, delegates in front - if ( selector ) { - handlers.splice( handlers.delegateCount++, 0, handleObj ); - } else { - handlers.push( handleObj ); - } - - // Keep track of which events have ever been used, for event optimization - jQuery.event.global[ type ] = true; - } - - }, - - // Detach an event or set of events from an element - remove: function( elem, types, handler, selector, mappedTypes ) { - - var j, origCount, tmp, - events, t, handleObj, - special, handlers, type, namespaces, origType, - elemData = dataPriv.hasData( elem ) && dataPriv.get( elem ); - - if ( !elemData || !( events = elemData.events ) ) { - return; - } - - // Once for each type.namespace in types; type may be omitted - types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; - t = types.length; - while ( t-- ) { - tmp = rtypenamespace.exec( types[ t ] ) || []; - type = origType = tmp[ 1 ]; - namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); - - // Unbind all events (on this namespace, if provided) for the element - if ( !type ) { - for ( type in events ) { - jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); - } - continue; - } - - special = jQuery.event.special[ type ] || {}; - type = ( selector ? special.delegateType : special.bindType ) || type; - handlers = events[ type ] || []; - tmp = tmp[ 2 ] && - new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ); - - // Remove matching events - origCount = j = handlers.length; - while ( j-- ) { - handleObj = handlers[ j ]; - - if ( ( mappedTypes || origType === handleObj.origType ) && - ( !handler || handler.guid === handleObj.guid ) && - ( !tmp || tmp.test( handleObj.namespace ) ) && - ( !selector || selector === handleObj.selector || - selector === "**" && handleObj.selector ) ) { - handlers.splice( j, 1 ); - - if ( handleObj.selector ) { - handlers.delegateCount--; - } - if ( special.remove ) { - special.remove.call( elem, handleObj ); - } - } - } - - // Remove generic event handler if we removed something and no more handlers exist - // (avoids potential for endless recursion during removal of special event handlers) - if ( origCount && !handlers.length ) { - if ( !special.teardown || - special.teardown.call( elem, namespaces, elemData.handle ) === false ) { - - jQuery.removeEvent( elem, type, elemData.handle ); - } - - delete events[ type ]; - } - } - - // Remove data and the expando if it's no longer used - if ( jQuery.isEmptyObject( events ) ) { - dataPriv.remove( elem, "handle events" ); - } - }, - - dispatch: function( nativeEvent ) { - - // Make a writable jQuery.Event from the native event object - var event = jQuery.event.fix( nativeEvent ); - - var i, j, ret, matched, handleObj, handlerQueue, - args = new Array( arguments.length ), - handlers = ( dataPriv.get( this, "events" ) || {} )[ event.type ] || [], - special = jQuery.event.special[ event.type ] || {}; - - // Use the fix-ed jQuery.Event rather than the (read-only) native event - args[ 0 ] = event; - - for ( i = 1; i < arguments.length; i++ ) { - args[ i ] = arguments[ i ]; - } - - event.delegateTarget = this; - - // Call the preDispatch hook for the mapped type, and let it bail if desired - if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { - return; - } - - // Determine handlers - handlerQueue = jQuery.event.handlers.call( this, event, handlers ); - - // Run delegates first; they may want to stop propagation beneath us - i = 0; - while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) { - event.currentTarget = matched.elem; - - j = 0; - while ( ( handleObj = matched.handlers[ j++ ] ) && - !event.isImmediatePropagationStopped() ) { - - // Triggered event must either 1) have no namespace, or 2) have namespace(s) - // a subset or equal to those in the bound event (both can have no namespace). - if ( !event.rnamespace || event.rnamespace.test( handleObj.namespace ) ) { - - event.handleObj = handleObj; - event.data = handleObj.data; - - ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle || - handleObj.handler ).apply( matched.elem, args ); - - if ( ret !== undefined ) { - if ( ( event.result = ret ) === false ) { - event.preventDefault(); - event.stopPropagation(); - } - } - } - } - } - - // Call the postDispatch hook for the mapped type - if ( special.postDispatch ) { - special.postDispatch.call( this, event ); - } - - return event.result; - }, - - handlers: function( event, handlers ) { - var i, handleObj, sel, matchedHandlers, matchedSelectors, - handlerQueue = [], - delegateCount = handlers.delegateCount, - cur = event.target; - - // Find delegate handlers - if ( delegateCount && - - // Support: IE <=9 - // Black-hole SVG instance trees (trac-13180) - cur.nodeType && - - // Support: Firefox <=42 - // Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861) - // https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click - // Support: IE 11 only - // ...but not arrow key "clicks" of radio inputs, which can have `button` -1 (gh-2343) - !( event.type === "click" && event.button >= 1 ) ) { - - for ( ; cur !== this; cur = cur.parentNode || this ) { - - // Don't check non-elements (#13208) - // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) - if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) { - matchedHandlers = []; - matchedSelectors = {}; - for ( i = 0; i < delegateCount; i++ ) { - handleObj = handlers[ i ]; - - // Don't conflict with Object.prototype properties (#13203) - sel = handleObj.selector + " "; - - if ( matchedSelectors[ sel ] === undefined ) { - matchedSelectors[ sel ] = handleObj.needsContext ? - jQuery( sel, this ).index( cur ) > -1 : - jQuery.find( sel, this, null, [ cur ] ).length; - } - if ( matchedSelectors[ sel ] ) { - matchedHandlers.push( handleObj ); - } - } - if ( matchedHandlers.length ) { - handlerQueue.push( { elem: cur, handlers: matchedHandlers } ); - } - } - } - } - - // Add the remaining (directly-bound) handlers - cur = this; - if ( delegateCount < handlers.length ) { - handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } ); - } - - return handlerQueue; - }, - - addProp: function( name, hook ) { - Object.defineProperty( jQuery.Event.prototype, name, { - enumerable: true, - configurable: true, - - get: jQuery.isFunction( hook ) ? - function() { - if ( this.originalEvent ) { - return hook( this.originalEvent ); - } - } : - function() { - if ( this.originalEvent ) { - return this.originalEvent[ name ]; - } - }, - - set: function( value ) { - Object.defineProperty( this, name, { - enumerable: true, - configurable: true, - writable: true, - value: value - } ); - } - } ); - }, - - fix: function( originalEvent ) { - return originalEvent[ jQuery.expando ] ? - originalEvent : - new jQuery.Event( originalEvent ); - }, - - special: { - load: { - - // Prevent triggered image.load events from bubbling to window.load - noBubble: true - }, - focus: { - - // Fire native event if possible so blur/focus sequence is correct - trigger: function() { - if ( this !== safeActiveElement() && this.focus ) { - this.focus(); - return false; - } - }, - delegateType: "focusin" - }, - blur: { - trigger: function() { - if ( this === safeActiveElement() && this.blur ) { - this.blur(); - return false; - } - }, - delegateType: "focusout" - }, - click: { - - // For checkbox, fire native event so checked state will be right - trigger: function() { - if ( this.type === "checkbox" && this.click && jQuery.nodeName( this, "input" ) ) { - this.click(); - return false; - } - }, - - // For cross-browser consistency, don't fire native .click() on links - _default: function( event ) { - return jQuery.nodeName( event.target, "a" ); - } - }, - - beforeunload: { - postDispatch: function( event ) { - - // Support: Firefox 20+ - // Firefox doesn't alert if the returnValue field is not set. - if ( event.result !== undefined && event.originalEvent ) { - event.originalEvent.returnValue = event.result; - } - } - } - } -}; - -jQuery.removeEvent = function( elem, type, handle ) { - - // This "if" is needed for plain objects - if ( elem.removeEventListener ) { - elem.removeEventListener( type, handle ); - } -}; - -jQuery.Event = function( src, props ) { - - // Allow instantiation without the 'new' keyword - if ( !( this instanceof jQuery.Event ) ) { - return new jQuery.Event( src, props ); - } - - // Event object - if ( src && src.type ) { - this.originalEvent = src; - this.type = src.type; - - // Events bubbling up the document may have been marked as prevented - // by a handler lower down the tree; reflect the correct value. - this.isDefaultPrevented = src.defaultPrevented || - src.defaultPrevented === undefined && - - // Support: Android <=2.3 only - src.returnValue === false ? - returnTrue : - returnFalse; - - // Create target properties - // Support: Safari <=6 - 7 only - // Target should not be a text node (#504, #13143) - this.target = ( src.target && src.target.nodeType === 3 ) ? - src.target.parentNode : - src.target; - - this.currentTarget = src.currentTarget; - this.relatedTarget = src.relatedTarget; - - // Event type - } else { - this.type = src; - } - - // Put explicitly provided properties onto the event object - if ( props ) { - jQuery.extend( this, props ); - } - - // Create a timestamp if incoming event doesn't have one - this.timeStamp = src && src.timeStamp || jQuery.now(); - - // Mark it as fixed - this[ jQuery.expando ] = true; -}; - -// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding -// https://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html -jQuery.Event.prototype = { - constructor: jQuery.Event, - isDefaultPrevented: returnFalse, - isPropagationStopped: returnFalse, - isImmediatePropagationStopped: returnFalse, - isSimulated: false, - - preventDefault: function() { - var e = this.originalEvent; - - this.isDefaultPrevented = returnTrue; - - if ( e && !this.isSimulated ) { - e.preventDefault(); - } - }, - stopPropagation: function() { - var e = this.originalEvent; - - this.isPropagationStopped = returnTrue; - - if ( e && !this.isSimulated ) { - e.stopPropagation(); - } - }, - stopImmediatePropagation: function() { - var e = this.originalEvent; - - this.isImmediatePropagationStopped = returnTrue; - - if ( e && !this.isSimulated ) { - e.stopImmediatePropagation(); - } - - this.stopPropagation(); - } -}; - -// Includes all common event props including KeyEvent and MouseEvent specific props -jQuery.each( { - altKey: true, - bubbles: true, - cancelable: true, - changedTouches: true, - ctrlKey: true, - detail: true, - eventPhase: true, - metaKey: true, - pageX: true, - pageY: true, - shiftKey: true, - view: true, - "char": true, - charCode: true, - key: true, - keyCode: true, - button: true, - buttons: true, - clientX: true, - clientY: true, - offsetX: true, - offsetY: true, - pointerId: true, - pointerType: true, - screenX: true, - screenY: true, - targetTouches: true, - toElement: true, - touches: true, - - which: function( event ) { - var button = event.button; - - // Add which for key events - if ( event.which == null && rkeyEvent.test( event.type ) ) { - return event.charCode != null ? event.charCode : event.keyCode; - } - - // Add which for click: 1 === left; 2 === middle; 3 === right - if ( !event.which && button !== undefined && rmouseEvent.test( event.type ) ) { - if ( button & 1 ) { - return 1; - } - - if ( button & 2 ) { - return 3; - } - - if ( button & 4 ) { - return 2; - } - - return 0; - } - - return event.which; - } -}, jQuery.event.addProp ); - -// Create mouseenter/leave events using mouseover/out and event-time checks -// so that event delegation works in jQuery. -// Do the same for pointerenter/pointerleave and pointerover/pointerout -// -// Support: Safari 7 only -// Safari sends mouseenter too often; see: -// https://bugs.chromium.org/p/chromium/issues/detail?id=470258 -// for the description of the bug (it existed in older Chrome versions as well). -jQuery.each( { - mouseenter: "mouseover", - mouseleave: "mouseout", - pointerenter: "pointerover", - pointerleave: "pointerout" -}, function( orig, fix ) { - jQuery.event.special[ orig ] = { - delegateType: fix, - bindType: fix, - - handle: function( event ) { - var ret, - target = this, - related = event.relatedTarget, - handleObj = event.handleObj; - - // For mouseenter/leave call the handler if related is outside the target. - // NB: No relatedTarget if the mouse left/entered the browser window - if ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) { - event.type = handleObj.origType; - ret = handleObj.handler.apply( this, arguments ); - event.type = fix; - } - return ret; - } - }; -} ); - -jQuery.fn.extend( { - - on: function( types, selector, data, fn ) { - return on( this, types, selector, data, fn ); - }, - one: function( types, selector, data, fn ) { - return on( this, types, selector, data, fn, 1 ); - }, - off: function( types, selector, fn ) { - var handleObj, type; - if ( types && types.preventDefault && types.handleObj ) { - - // ( event ) dispatched jQuery.Event - handleObj = types.handleObj; - jQuery( types.delegateTarget ).off( - handleObj.namespace ? - handleObj.origType + "." + handleObj.namespace : - handleObj.origType, - handleObj.selector, - handleObj.handler - ); - return this; - } - if ( typeof types === "object" ) { - - // ( types-object [, selector] ) - for ( type in types ) { - this.off( type, selector, types[ type ] ); - } - return this; - } - if ( selector === false || typeof selector === "function" ) { - - // ( types [, fn] ) - fn = selector; - selector = undefined; - } - if ( fn === false ) { - fn = returnFalse; - } - return this.each( function() { - jQuery.event.remove( this, types, fn, selector ); - } ); - } -} ); - - -var - - /* eslint-disable max-len */ - - // See https://github.com/eslint/eslint/issues/3229 - rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([a-z][^\/\0>\x20\t\r\n\f]*)[^>]*)\/>/gi, - - /* eslint-enable */ - - // Support: IE <=10 - 11, Edge 12 - 13 - // In IE/Edge using regex groups here causes severe slowdowns. - // See https://connect.microsoft.com/IE/feedback/details/1736512/ - rnoInnerhtml = /\s*$/g; - -function manipulationTarget( elem, content ) { - if ( jQuery.nodeName( elem, "table" ) && - jQuery.nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ) { - - return elem.getElementsByTagName( "tbody" )[ 0 ] || elem; - } - - return elem; -} - -// Replace/restore the type attribute of script elements for safe DOM manipulation -function disableScript( elem ) { - elem.type = ( elem.getAttribute( "type" ) !== null ) + "/" + elem.type; - return elem; -} -function restoreScript( elem ) { - var match = rscriptTypeMasked.exec( elem.type ); - - if ( match ) { - elem.type = match[ 1 ]; - } else { - elem.removeAttribute( "type" ); - } - - return elem; -} - -function cloneCopyEvent( src, dest ) { - var i, l, type, pdataOld, pdataCur, udataOld, udataCur, events; - - if ( dest.nodeType !== 1 ) { - return; - } - - // 1. Copy private data: events, handlers, etc. - if ( dataPriv.hasData( src ) ) { - pdataOld = dataPriv.access( src ); - pdataCur = dataPriv.set( dest, pdataOld ); - events = pdataOld.events; - - if ( events ) { - delete pdataCur.handle; - pdataCur.events = {}; - - for ( type in events ) { - for ( i = 0, l = events[ type ].length; i < l; i++ ) { - jQuery.event.add( dest, type, events[ type ][ i ] ); - } - } - } - } - - // 2. Copy user data - if ( dataUser.hasData( src ) ) { - udataOld = dataUser.access( src ); - udataCur = jQuery.extend( {}, udataOld ); - - dataUser.set( dest, udataCur ); - } -} - -// Fix IE bugs, see support tests -function fixInput( src, dest ) { - var nodeName = dest.nodeName.toLowerCase(); - - // Fails to persist the checked state of a cloned checkbox or radio button. - if ( nodeName === "input" && rcheckableType.test( src.type ) ) { - dest.checked = src.checked; - - // Fails to return the selected option to the default selected state when cloning options - } else if ( nodeName === "input" || nodeName === "textarea" ) { - dest.defaultValue = src.defaultValue; - } -} - -function domManip( collection, args, callback, ignored ) { - - // Flatten any nested arrays - args = concat.apply( [], args ); - - var fragment, first, scripts, hasScripts, node, doc, - i = 0, - l = collection.length, - iNoClone = l - 1, - value = args[ 0 ], - isFunction = jQuery.isFunction( value ); - - // We can't cloneNode fragments that contain checked, in WebKit - if ( isFunction || - ( l > 1 && typeof value === "string" && - !support.checkClone && rchecked.test( value ) ) ) { - return collection.each( function( index ) { - var self = collection.eq( index ); - if ( isFunction ) { - args[ 0 ] = value.call( this, index, self.html() ); - } - domManip( self, args, callback, ignored ); - } ); - } - - if ( l ) { - fragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored ); - first = fragment.firstChild; - - if ( fragment.childNodes.length === 1 ) { - fragment = first; - } - - // Require either new content or an interest in ignored elements to invoke the callback - if ( first || ignored ) { - scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); - hasScripts = scripts.length; - - // Use the original fragment for the last item - // instead of the first because it can end up - // being emptied incorrectly in certain situations (#8070). - for ( ; i < l; i++ ) { - node = fragment; - - if ( i !== iNoClone ) { - node = jQuery.clone( node, true, true ); - - // Keep references to cloned scripts for later restoration - if ( hasScripts ) { - - // Support: Android <=4.0 only, PhantomJS 1 only - // push.apply(_, arraylike) throws on ancient WebKit - jQuery.merge( scripts, getAll( node, "script" ) ); - } - } - - callback.call( collection[ i ], node, i ); - } - - if ( hasScripts ) { - doc = scripts[ scripts.length - 1 ].ownerDocument; - - // Reenable scripts - jQuery.map( scripts, restoreScript ); - - // Evaluate executable scripts on first document insertion - for ( i = 0; i < hasScripts; i++ ) { - node = scripts[ i ]; - if ( rscriptType.test( node.type || "" ) && - !dataPriv.access( node, "globalEval" ) && - jQuery.contains( doc, node ) ) { - - if ( node.src ) { - - // Optional AJAX dependency, but won't run scripts if not present - if ( jQuery._evalUrl ) { - jQuery._evalUrl( node.src ); - } - } else { - DOMEval( node.textContent.replace( rcleanScript, "" ), doc ); - } - } - } - } - } - } - - return collection; -} - -function remove( elem, selector, keepData ) { - var node, - nodes = selector ? jQuery.filter( selector, elem ) : elem, - i = 0; - - for ( ; ( node = nodes[ i ] ) != null; i++ ) { - if ( !keepData && node.nodeType === 1 ) { - jQuery.cleanData( getAll( node ) ); - } - - if ( node.parentNode ) { - if ( keepData && jQuery.contains( node.ownerDocument, node ) ) { - setGlobalEval( getAll( node, "script" ) ); - } - node.parentNode.removeChild( node ); - } - } - - return elem; -} - -jQuery.extend( { - htmlPrefilter: function( html ) { - return html.replace( rxhtmlTag, "<$1>" ); - }, - - clone: function( elem, dataAndEvents, deepDataAndEvents ) { - var i, l, srcElements, destElements, - clone = elem.cloneNode( true ), - inPage = jQuery.contains( elem.ownerDocument, elem ); - - // Fix IE cloning issues - if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) && - !jQuery.isXMLDoc( elem ) ) { - - // We eschew Sizzle here for performance reasons: https://jsperf.com/getall-vs-sizzle/2 - destElements = getAll( clone ); - srcElements = getAll( elem ); - - for ( i = 0, l = srcElements.length; i < l; i++ ) { - fixInput( srcElements[ i ], destElements[ i ] ); - } - } - - // Copy the events from the original to the clone - if ( dataAndEvents ) { - if ( deepDataAndEvents ) { - srcElements = srcElements || getAll( elem ); - destElements = destElements || getAll( clone ); - - for ( i = 0, l = srcElements.length; i < l; i++ ) { - cloneCopyEvent( srcElements[ i ], destElements[ i ] ); - } - } else { - cloneCopyEvent( elem, clone ); - } - } - - // Preserve script evaluation history - destElements = getAll( clone, "script" ); - if ( destElements.length > 0 ) { - setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); - } - - // Return the cloned set - return clone; - }, - - cleanData: function( elems ) { - var data, elem, type, - special = jQuery.event.special, - i = 0; - - for ( ; ( elem = elems[ i ] ) !== undefined; i++ ) { - if ( acceptData( elem ) ) { - if ( ( data = elem[ dataPriv.expando ] ) ) { - if ( data.events ) { - for ( type in data.events ) { - if ( special[ type ] ) { - jQuery.event.remove( elem, type ); - - // This is a shortcut to avoid jQuery.event.remove's overhead - } else { - jQuery.removeEvent( elem, type, data.handle ); - } - } - } - - // Support: Chrome <=35 - 45+ - // Assign undefined instead of using delete, see Data#remove - elem[ dataPriv.expando ] = undefined; - } - if ( elem[ dataUser.expando ] ) { - - // Support: Chrome <=35 - 45+ - // Assign undefined instead of using delete, see Data#remove - elem[ dataUser.expando ] = undefined; - } - } - } - } -} ); - -jQuery.fn.extend( { - detach: function( selector ) { - return remove( this, selector, true ); - }, - - remove: function( selector ) { - return remove( this, selector ); - }, - - text: function( value ) { - return access( this, function( value ) { - return value === undefined ? - jQuery.text( this ) : - this.empty().each( function() { - if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { - this.textContent = value; - } - } ); - }, null, value, arguments.length ); - }, - - append: function() { - return domManip( this, arguments, function( elem ) { - if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { - var target = manipulationTarget( this, elem ); - target.appendChild( elem ); - } - } ); - }, - - prepend: function() { - return domManip( this, arguments, function( elem ) { - if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { - var target = manipulationTarget( this, elem ); - target.insertBefore( elem, target.firstChild ); - } - } ); - }, - - before: function() { - return domManip( this, arguments, function( elem ) { - if ( this.parentNode ) { - this.parentNode.insertBefore( elem, this ); - } - } ); - }, - - after: function() { - return domManip( this, arguments, function( elem ) { - if ( this.parentNode ) { - this.parentNode.insertBefore( elem, this.nextSibling ); - } - } ); - }, - - empty: function() { - var elem, - i = 0; - - for ( ; ( elem = this[ i ] ) != null; i++ ) { - if ( elem.nodeType === 1 ) { - - // Prevent memory leaks - jQuery.cleanData( getAll( elem, false ) ); - - // Remove any remaining nodes - elem.textContent = ""; - } - } - - return this; - }, - - clone: function( dataAndEvents, deepDataAndEvents ) { - dataAndEvents = dataAndEvents == null ? false : dataAndEvents; - deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; - - return this.map( function() { - return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); - } ); - }, - - html: function( value ) { - return access( this, function( value ) { - var elem = this[ 0 ] || {}, - i = 0, - l = this.length; - - if ( value === undefined && elem.nodeType === 1 ) { - return elem.innerHTML; - } - - // See if we can take a shortcut and just use innerHTML - if ( typeof value === "string" && !rnoInnerhtml.test( value ) && - !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) { - - value = jQuery.htmlPrefilter( value ); - - try { - for ( ; i < l; i++ ) { - elem = this[ i ] || {}; - - // Remove element nodes and prevent memory leaks - if ( elem.nodeType === 1 ) { - jQuery.cleanData( getAll( elem, false ) ); - elem.innerHTML = value; - } - } - - elem = 0; - - // If using innerHTML throws an exception, use the fallback method - } catch ( e ) {} - } - - if ( elem ) { - this.empty().append( value ); - } - }, null, value, arguments.length ); - }, - - replaceWith: function() { - var ignored = []; - - // Make the changes, replacing each non-ignored context element with the new content - return domManip( this, arguments, function( elem ) { - var parent = this.parentNode; - - if ( jQuery.inArray( this, ignored ) < 0 ) { - jQuery.cleanData( getAll( this ) ); - if ( parent ) { - parent.replaceChild( elem, this ); - } - } - - // Force callback invocation - }, ignored ); - } -} ); - -jQuery.each( { - appendTo: "append", - prependTo: "prepend", - insertBefore: "before", - insertAfter: "after", - replaceAll: "replaceWith" -}, function( name, original ) { - jQuery.fn[ name ] = function( selector ) { - var elems, - ret = [], - insert = jQuery( selector ), - last = insert.length - 1, - i = 0; - - for ( ; i <= last; i++ ) { - elems = i === last ? this : this.clone( true ); - jQuery( insert[ i ] )[ original ]( elems ); - - // Support: Android <=4.0 only, PhantomJS 1 only - // .get() because push.apply(_, arraylike) throws on ancient WebKit - push.apply( ret, elems.get() ); - } - - return this.pushStack( ret ); - }; -} ); -var rmargin = ( /^margin/ ); - -var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" ); - -var getStyles = function( elem ) { - - // Support: IE <=11 only, Firefox <=30 (#15098, #14150) - // IE throws on elements created in popups - // FF meanwhile throws on frame elements through "defaultView.getComputedStyle" - var view = elem.ownerDocument.defaultView; - - if ( !view || !view.opener ) { - view = window; - } - - return view.getComputedStyle( elem ); - }; - - - -( function() { - - // Executing both pixelPosition & boxSizingReliable tests require only one layout - // so they're executed at the same time to save the second computation. - function computeStyleTests() { - - // This is a singleton, we need to execute it only once - if ( !div ) { - return; - } - - div.style.cssText = - "box-sizing:border-box;" + - "position:relative;display:block;" + - "margin:auto;border:1px;padding:1px;" + - "top:1%;width:50%"; - div.innerHTML = ""; - documentElement.appendChild( container ); - - var divStyle = window.getComputedStyle( div ); - pixelPositionVal = divStyle.top !== "1%"; - - // Support: Android 4.0 - 4.3 only, Firefox <=3 - 44 - reliableMarginLeftVal = divStyle.marginLeft === "2px"; - boxSizingReliableVal = divStyle.width === "4px"; - - // Support: Android 4.0 - 4.3 only - // Some styles come back with percentage values, even though they shouldn't - div.style.marginRight = "50%"; - pixelMarginRightVal = divStyle.marginRight === "4px"; - - documentElement.removeChild( container ); - - // Nullify the div so it wouldn't be stored in the memory and - // it will also be a sign that checks already performed - div = null; - } - - var pixelPositionVal, boxSizingReliableVal, pixelMarginRightVal, reliableMarginLeftVal, - container = document.createElement( "div" ), - div = document.createElement( "div" ); - - // Finish early in limited (non-browser) environments - if ( !div.style ) { - return; - } - - // Support: IE <=9 - 11 only - // Style of cloned element affects source element cloned (#8908) - div.style.backgroundClip = "content-box"; - div.cloneNode( true ).style.backgroundClip = ""; - support.clearCloneStyle = div.style.backgroundClip === "content-box"; - - container.style.cssText = "border:0;width:8px;height:0;top:0;left:-9999px;" + - "padding:0;margin-top:1px;position:absolute"; - container.appendChild( div ); - - jQuery.extend( support, { - pixelPosition: function() { - computeStyleTests(); - return pixelPositionVal; - }, - boxSizingReliable: function() { - computeStyleTests(); - return boxSizingReliableVal; - }, - pixelMarginRight: function() { - computeStyleTests(); - return pixelMarginRightVal; - }, - reliableMarginLeft: function() { - computeStyleTests(); - return reliableMarginLeftVal; - } - } ); -} )(); - - -function curCSS( elem, name, computed ) { - var width, minWidth, maxWidth, ret, - style = elem.style; - - computed = computed || getStyles( elem ); - - // Support: IE <=9 only - // getPropertyValue is only needed for .css('filter') (#12537) - if ( computed ) { - ret = computed.getPropertyValue( name ) || computed[ name ]; - - if ( ret === "" && !jQuery.contains( elem.ownerDocument, elem ) ) { - ret = jQuery.style( elem, name ); - } - - // A tribute to the "awesome hack by Dean Edwards" - // Android Browser returns percentage for some values, - // but width seems to be reliably pixels. - // This is against the CSSOM draft spec: - // https://drafts.csswg.org/cssom/#resolved-values - if ( !support.pixelMarginRight() && rnumnonpx.test( ret ) && rmargin.test( name ) ) { - - // Remember the original values - width = style.width; - minWidth = style.minWidth; - maxWidth = style.maxWidth; - - // Put in the new values to get a computed value out - style.minWidth = style.maxWidth = style.width = ret; - ret = computed.width; - - // Revert the changed values - style.width = width; - style.minWidth = minWidth; - style.maxWidth = maxWidth; - } - } - - return ret !== undefined ? - - // Support: IE <=9 - 11 only - // IE returns zIndex value as an integer. - ret + "" : - ret; -} - - -function addGetHookIf( conditionFn, hookFn ) { - - // Define the hook, we'll check on the first run if it's really needed. - return { - get: function() { - if ( conditionFn() ) { - - // Hook not needed (or it's not possible to use it due - // to missing dependency), remove it. - delete this.get; - return; - } - - // Hook needed; redefine it so that the support test is not executed again. - return ( this.get = hookFn ).apply( this, arguments ); - } - }; -} - - -var - - // Swappable if display is none or starts with table - // except "table", "table-cell", or "table-caption" - // See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display - rdisplayswap = /^(none|table(?!-c[ea]).+)/, - cssShow = { position: "absolute", visibility: "hidden", display: "block" }, - cssNormalTransform = { - letterSpacing: "0", - fontWeight: "400" - }, - - cssPrefixes = [ "Webkit", "Moz", "ms" ], - emptyStyle = document.createElement( "div" ).style; - -// Return a css property mapped to a potentially vendor prefixed property -function vendorPropName( name ) { - - // Shortcut for names that are not vendor prefixed - if ( name in emptyStyle ) { - return name; - } - - // Check for vendor prefixed names - var capName = name[ 0 ].toUpperCase() + name.slice( 1 ), - i = cssPrefixes.length; - - while ( i-- ) { - name = cssPrefixes[ i ] + capName; - if ( name in emptyStyle ) { - return name; - } - } -} - -function setPositiveNumber( elem, value, subtract ) { - - // Any relative (+/-) values have already been - // normalized at this point - var matches = rcssNum.exec( value ); - return matches ? - - // Guard against undefined "subtract", e.g., when used as in cssHooks - Math.max( 0, matches[ 2 ] - ( subtract || 0 ) ) + ( matches[ 3 ] || "px" ) : - value; -} - -function augmentWidthOrHeight( elem, name, extra, isBorderBox, styles ) { - var i, - val = 0; - - // If we already have the right measurement, avoid augmentation - if ( extra === ( isBorderBox ? "border" : "content" ) ) { - i = 4; - - // Otherwise initialize for horizontal or vertical properties - } else { - i = name === "width" ? 1 : 0; - } - - for ( ; i < 4; i += 2 ) { - - // Both box models exclude margin, so add it if we want it - if ( extra === "margin" ) { - val += jQuery.css( elem, extra + cssExpand[ i ], true, styles ); - } - - if ( isBorderBox ) { - - // border-box includes padding, so remove it if we want content - if ( extra === "content" ) { - val -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); - } - - // At this point, extra isn't border nor margin, so remove border - if ( extra !== "margin" ) { - val -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); - } - } else { - - // At this point, extra isn't content, so add padding - val += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); - - // At this point, extra isn't content nor padding, so add border - if ( extra !== "padding" ) { - val += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); - } - } - } - - return val; -} - -function getWidthOrHeight( elem, name, extra ) { - - // Start with offset property, which is equivalent to the border-box value - var val, - valueIsBorderBox = true, - styles = getStyles( elem ), - isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box"; - - // Support: IE <=11 only - // Running getBoundingClientRect on a disconnected node - // in IE throws an error. - if ( elem.getClientRects().length ) { - val = elem.getBoundingClientRect()[ name ]; - } - - // Some non-html elements return undefined for offsetWidth, so check for null/undefined - // svg - https://bugzilla.mozilla.org/show_bug.cgi?id=649285 - // MathML - https://bugzilla.mozilla.org/show_bug.cgi?id=491668 - if ( val <= 0 || val == null ) { - - // Fall back to computed then uncomputed css if necessary - val = curCSS( elem, name, styles ); - if ( val < 0 || val == null ) { - val = elem.style[ name ]; - } - - // Computed unit is not pixels. Stop here and return. - if ( rnumnonpx.test( val ) ) { - return val; - } - - // Check for style in case a browser which returns unreliable values - // for getComputedStyle silently falls back to the reliable elem.style - valueIsBorderBox = isBorderBox && - ( support.boxSizingReliable() || val === elem.style[ name ] ); - - // Normalize "", auto, and prepare for extra - val = parseFloat( val ) || 0; - } - - // Use the active box-sizing model to add/subtract irrelevant styles - return ( val + - augmentWidthOrHeight( - elem, - name, - extra || ( isBorderBox ? "border" : "content" ), - valueIsBorderBox, - styles - ) - ) + "px"; -} - -jQuery.extend( { - - // Add in style property hooks for overriding the default - // behavior of getting and setting a style property - cssHooks: { - opacity: { - get: function( elem, computed ) { - if ( computed ) { - - // We should always get a number back from opacity - var ret = curCSS( elem, "opacity" ); - return ret === "" ? "1" : ret; - } - } - } - }, - - // Don't automatically add "px" to these possibly-unitless properties - cssNumber: { - "animationIterationCount": true, - "columnCount": true, - "fillOpacity": true, - "flexGrow": true, - "flexShrink": true, - "fontWeight": true, - "lineHeight": true, - "opacity": true, - "order": true, - "orphans": true, - "widows": true, - "zIndex": true, - "zoom": true - }, - - // Add in properties whose names you wish to fix before - // setting or getting the value - cssProps: { - "float": "cssFloat" - }, - - // Get and set the style property on a DOM Node - style: function( elem, name, value, extra ) { - - // Don't set styles on text and comment nodes - if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { - return; - } - - // Make sure that we're working with the right name - var ret, type, hooks, - origName = jQuery.camelCase( name ), - style = elem.style; - - name = jQuery.cssProps[ origName ] || - ( jQuery.cssProps[ origName ] = vendorPropName( origName ) || origName ); - - // Gets hook for the prefixed version, then unprefixed version - hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; - - // Check if we're setting a value - if ( value !== undefined ) { - type = typeof value; - - // Convert "+=" or "-=" to relative numbers (#7345) - if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) { - value = adjustCSS( elem, name, ret ); - - // Fixes bug #9237 - type = "number"; - } - - // Make sure that null and NaN values aren't set (#7116) - if ( value == null || value !== value ) { - return; - } - - // If a number was passed in, add the unit (except for certain CSS properties) - if ( type === "number" ) { - value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" ); - } - - // background-* props affect original clone's values - if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) { - style[ name ] = "inherit"; - } - - // If a hook was provided, use that value, otherwise just set the specified value - if ( !hooks || !( "set" in hooks ) || - ( value = hooks.set( elem, value, extra ) ) !== undefined ) { - - style[ name ] = value; - } - - } else { - - // If a hook was provided get the non-computed value from there - if ( hooks && "get" in hooks && - ( ret = hooks.get( elem, false, extra ) ) !== undefined ) { - - return ret; - } - - // Otherwise just get the value from the style object - return style[ name ]; - } - }, - - css: function( elem, name, extra, styles ) { - var val, num, hooks, - origName = jQuery.camelCase( name ); - - // Make sure that we're working with the right name - name = jQuery.cssProps[ origName ] || - ( jQuery.cssProps[ origName ] = vendorPropName( origName ) || origName ); - - // Try prefixed name followed by the unprefixed name - hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; - - // If a hook was provided get the computed value from there - if ( hooks && "get" in hooks ) { - val = hooks.get( elem, true, extra ); - } - - // Otherwise, if a way to get the computed value exists, use that - if ( val === undefined ) { - val = curCSS( elem, name, styles ); - } - - // Convert "normal" to computed value - if ( val === "normal" && name in cssNormalTransform ) { - val = cssNormalTransform[ name ]; - } - - // Make numeric if forced or a qualifier was provided and val looks numeric - if ( extra === "" || extra ) { - num = parseFloat( val ); - return extra === true || isFinite( num ) ? num || 0 : val; - } - return val; - } -} ); - -jQuery.each( [ "height", "width" ], function( i, name ) { - jQuery.cssHooks[ name ] = { - get: function( elem, computed, extra ) { - if ( computed ) { - - // Certain elements can have dimension info if we invisibly show them - // but it must have a current display style that would benefit - return rdisplayswap.test( jQuery.css( elem, "display" ) ) && - - // Support: Safari 8+ - // Table columns in Safari have non-zero offsetWidth & zero - // getBoundingClientRect().width unless display is changed. - // Support: IE <=11 only - // Running getBoundingClientRect on a disconnected node - // in IE throws an error. - ( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ? - swap( elem, cssShow, function() { - return getWidthOrHeight( elem, name, extra ); - } ) : - getWidthOrHeight( elem, name, extra ); - } - }, - - set: function( elem, value, extra ) { - var matches, - styles = extra && getStyles( elem ), - subtract = extra && augmentWidthOrHeight( - elem, - name, - extra, - jQuery.css( elem, "boxSizing", false, styles ) === "border-box", - styles - ); - - // Convert to pixels if value adjustment is needed - if ( subtract && ( matches = rcssNum.exec( value ) ) && - ( matches[ 3 ] || "px" ) !== "px" ) { - - elem.style[ name ] = value; - value = jQuery.css( elem, name ); - } - - return setPositiveNumber( elem, value, subtract ); - } - }; -} ); - -jQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft, - function( elem, computed ) { - if ( computed ) { - return ( parseFloat( curCSS( elem, "marginLeft" ) ) || - elem.getBoundingClientRect().left - - swap( elem, { marginLeft: 0 }, function() { - return elem.getBoundingClientRect().left; - } ) - ) + "px"; - } - } -); - -// These hooks are used by animate to expand properties -jQuery.each( { - margin: "", - padding: "", - border: "Width" -}, function( prefix, suffix ) { - jQuery.cssHooks[ prefix + suffix ] = { - expand: function( value ) { - var i = 0, - expanded = {}, - - // Assumes a single number if not a string - parts = typeof value === "string" ? value.split( " " ) : [ value ]; - - for ( ; i < 4; i++ ) { - expanded[ prefix + cssExpand[ i ] + suffix ] = - parts[ i ] || parts[ i - 2 ] || parts[ 0 ]; - } - - return expanded; - } - }; - - if ( !rmargin.test( prefix ) ) { - jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber; - } -} ); - -jQuery.fn.extend( { - css: function( name, value ) { - return access( this, function( elem, name, value ) { - var styles, len, - map = {}, - i = 0; - - if ( jQuery.isArray( name ) ) { - styles = getStyles( elem ); - len = name.length; - - for ( ; i < len; i++ ) { - map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles ); - } - - return map; - } - - return value !== undefined ? - jQuery.style( elem, name, value ) : - jQuery.css( elem, name ); - }, name, value, arguments.length > 1 ); - } -} ); - - -function Tween( elem, options, prop, end, easing ) { - return new Tween.prototype.init( elem, options, prop, end, easing ); -} -jQuery.Tween = Tween; - -Tween.prototype = { - constructor: Tween, - init: function( elem, options, prop, end, easing, unit ) { - this.elem = elem; - this.prop = prop; - this.easing = easing || jQuery.easing._default; - this.options = options; - this.start = this.now = this.cur(); - this.end = end; - this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" ); - }, - cur: function() { - var hooks = Tween.propHooks[ this.prop ]; - - return hooks && hooks.get ? - hooks.get( this ) : - Tween.propHooks._default.get( this ); - }, - run: function( percent ) { - var eased, - hooks = Tween.propHooks[ this.prop ]; - - if ( this.options.duration ) { - this.pos = eased = jQuery.easing[ this.easing ]( - percent, this.options.duration * percent, 0, 1, this.options.duration - ); - } else { - this.pos = eased = percent; - } - this.now = ( this.end - this.start ) * eased + this.start; - - if ( this.options.step ) { - this.options.step.call( this.elem, this.now, this ); - } - - if ( hooks && hooks.set ) { - hooks.set( this ); - } else { - Tween.propHooks._default.set( this ); - } - return this; - } -}; - -Tween.prototype.init.prototype = Tween.prototype; - -Tween.propHooks = { - _default: { - get: function( tween ) { - var result; - - // Use a property on the element directly when it is not a DOM element, - // or when there is no matching style property that exists. - if ( tween.elem.nodeType !== 1 || - tween.elem[ tween.prop ] != null && tween.elem.style[ tween.prop ] == null ) { - return tween.elem[ tween.prop ]; - } - - // Passing an empty string as a 3rd parameter to .css will automatically - // attempt a parseFloat and fallback to a string if the parse fails. - // Simple values such as "10px" are parsed to Float; - // complex values such as "rotate(1rad)" are returned as-is. - result = jQuery.css( tween.elem, tween.prop, "" ); - - // Empty strings, null, undefined and "auto" are converted to 0. - return !result || result === "auto" ? 0 : result; - }, - set: function( tween ) { - - // Use step hook for back compat. - // Use cssHook if its there. - // Use .style if available and use plain properties where available. - if ( jQuery.fx.step[ tween.prop ] ) { - jQuery.fx.step[ tween.prop ]( tween ); - } else if ( tween.elem.nodeType === 1 && - ( tween.elem.style[ jQuery.cssProps[ tween.prop ] ] != null || - jQuery.cssHooks[ tween.prop ] ) ) { - jQuery.style( tween.elem, tween.prop, tween.now + tween.unit ); - } else { - tween.elem[ tween.prop ] = tween.now; - } - } - } -}; - -// Support: IE <=9 only -// Panic based approach to setting things on disconnected nodes -Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = { - set: function( tween ) { - if ( tween.elem.nodeType && tween.elem.parentNode ) { - tween.elem[ tween.prop ] = tween.now; - } - } -}; - -jQuery.easing = { - linear: function( p ) { - return p; - }, - swing: function( p ) { - return 0.5 - Math.cos( p * Math.PI ) / 2; - }, - _default: "swing" -}; - -jQuery.fx = Tween.prototype.init; - -// Back compat <1.8 extension point -jQuery.fx.step = {}; - - - - -var - fxNow, timerId, - rfxtypes = /^(?:toggle|show|hide)$/, - rrun = /queueHooks$/; - -function raf() { - if ( timerId ) { - window.requestAnimationFrame( raf ); - jQuery.fx.tick(); - } -} - -// Animations created synchronously will run synchronously -function createFxNow() { - window.setTimeout( function() { - fxNow = undefined; - } ); - return ( fxNow = jQuery.now() ); -} - -// Generate parameters to create a standard animation -function genFx( type, includeWidth ) { - var which, - i = 0, - attrs = { height: type }; - - // If we include width, step value is 1 to do all cssExpand values, - // otherwise step value is 2 to skip over Left and Right - includeWidth = includeWidth ? 1 : 0; - for ( ; i < 4; i += 2 - includeWidth ) { - which = cssExpand[ i ]; - attrs[ "margin" + which ] = attrs[ "padding" + which ] = type; - } - - if ( includeWidth ) { - attrs.opacity = attrs.width = type; - } - - return attrs; -} - -function createTween( value, prop, animation ) { - var tween, - collection = ( Animation.tweeners[ prop ] || [] ).concat( Animation.tweeners[ "*" ] ), - index = 0, - length = collection.length; - for ( ; index < length; index++ ) { - if ( ( tween = collection[ index ].call( animation, prop, value ) ) ) { - - // We're done with this property - return tween; - } - } -} - -function defaultPrefilter( elem, props, opts ) { - var prop, value, toggle, hooks, oldfire, propTween, restoreDisplay, display, - isBox = "width" in props || "height" in props, - anim = this, - orig = {}, - style = elem.style, - hidden = elem.nodeType && isHiddenWithinTree( elem ), - dataShow = dataPriv.get( elem, "fxshow" ); - - // Queue-skipping animations hijack the fx hooks - if ( !opts.queue ) { - hooks = jQuery._queueHooks( elem, "fx" ); - if ( hooks.unqueued == null ) { - hooks.unqueued = 0; - oldfire = hooks.empty.fire; - hooks.empty.fire = function() { - if ( !hooks.unqueued ) { - oldfire(); - } - }; - } - hooks.unqueued++; - - anim.always( function() { - - // Ensure the complete handler is called before this completes - anim.always( function() { - hooks.unqueued--; - if ( !jQuery.queue( elem, "fx" ).length ) { - hooks.empty.fire(); - } - } ); - } ); - } - - // Detect show/hide animations - for ( prop in props ) { - value = props[ prop ]; - if ( rfxtypes.test( value ) ) { - delete props[ prop ]; - toggle = toggle || value === "toggle"; - if ( value === ( hidden ? "hide" : "show" ) ) { - - // Pretend to be hidden if this is a "show" and - // there is still data from a stopped show/hide - if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) { - hidden = true; - - // Ignore all other no-op show/hide data - } else { - continue; - } - } - orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop ); - } - } - - // Bail out if this is a no-op like .hide().hide() - propTween = !jQuery.isEmptyObject( props ); - if ( !propTween && jQuery.isEmptyObject( orig ) ) { - return; - } - - // Restrict "overflow" and "display" styles during box animations - if ( isBox && elem.nodeType === 1 ) { - - // Support: IE <=9 - 11, Edge 12 - 13 - // Record all 3 overflow attributes because IE does not infer the shorthand - // from identically-valued overflowX and overflowY - opts.overflow = [ style.overflow, style.overflowX, style.overflowY ]; - - // Identify a display type, preferring old show/hide data over the CSS cascade - restoreDisplay = dataShow && dataShow.display; - if ( restoreDisplay == null ) { - restoreDisplay = dataPriv.get( elem, "display" ); - } - display = jQuery.css( elem, "display" ); - if ( display === "none" ) { - if ( restoreDisplay ) { - display = restoreDisplay; - } else { - - // Get nonempty value(s) by temporarily forcing visibility - showHide( [ elem ], true ); - restoreDisplay = elem.style.display || restoreDisplay; - display = jQuery.css( elem, "display" ); - showHide( [ elem ] ); - } - } - - // Animate inline elements as inline-block - if ( display === "inline" || display === "inline-block" && restoreDisplay != null ) { - if ( jQuery.css( elem, "float" ) === "none" ) { - - // Restore the original display value at the end of pure show/hide animations - if ( !propTween ) { - anim.done( function() { - style.display = restoreDisplay; - } ); - if ( restoreDisplay == null ) { - display = style.display; - restoreDisplay = display === "none" ? "" : display; - } - } - style.display = "inline-block"; - } - } - } - - if ( opts.overflow ) { - style.overflow = "hidden"; - anim.always( function() { - style.overflow = opts.overflow[ 0 ]; - style.overflowX = opts.overflow[ 1 ]; - style.overflowY = opts.overflow[ 2 ]; - } ); - } - - // Implement show/hide animations - propTween = false; - for ( prop in orig ) { - - // General show/hide setup for this element animation - if ( !propTween ) { - if ( dataShow ) { - if ( "hidden" in dataShow ) { - hidden = dataShow.hidden; - } - } else { - dataShow = dataPriv.access( elem, "fxshow", { display: restoreDisplay } ); - } - - // Store hidden/visible for toggle so `.stop().toggle()` "reverses" - if ( toggle ) { - dataShow.hidden = !hidden; - } - - // Show elements before animating them - if ( hidden ) { - showHide( [ elem ], true ); - } - - /* eslint-disable no-loop-func */ - - anim.done( function() { - - /* eslint-enable no-loop-func */ - - // The final step of a "hide" animation is actually hiding the element - if ( !hidden ) { - showHide( [ elem ] ); - } - dataPriv.remove( elem, "fxshow" ); - for ( prop in orig ) { - jQuery.style( elem, prop, orig[ prop ] ); - } - } ); - } - - // Per-property setup - propTween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim ); - if ( !( prop in dataShow ) ) { - dataShow[ prop ] = propTween.start; - if ( hidden ) { - propTween.end = propTween.start; - propTween.start = 0; - } - } - } -} - -function propFilter( props, specialEasing ) { - var index, name, easing, value, hooks; - - // camelCase, specialEasing and expand cssHook pass - for ( index in props ) { - name = jQuery.camelCase( index ); - easing = specialEasing[ name ]; - value = props[ index ]; - if ( jQuery.isArray( value ) ) { - easing = value[ 1 ]; - value = props[ index ] = value[ 0 ]; - } - - if ( index !== name ) { - props[ name ] = value; - delete props[ index ]; - } - - hooks = jQuery.cssHooks[ name ]; - if ( hooks && "expand" in hooks ) { - value = hooks.expand( value ); - delete props[ name ]; - - // Not quite $.extend, this won't overwrite existing keys. - // Reusing 'index' because we have the correct "name" - for ( index in value ) { - if ( !( index in props ) ) { - props[ index ] = value[ index ]; - specialEasing[ index ] = easing; - } - } - } else { - specialEasing[ name ] = easing; - } - } -} - -function Animation( elem, properties, options ) { - var result, - stopped, - index = 0, - length = Animation.prefilters.length, - deferred = jQuery.Deferred().always( function() { - - // Don't match elem in the :animated selector - delete tick.elem; - } ), - tick = function() { - if ( stopped ) { - return false; - } - var currentTime = fxNow || createFxNow(), - remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ), - - // Support: Android 2.3 only - // Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (#12497) - temp = remaining / animation.duration || 0, - percent = 1 - temp, - index = 0, - length = animation.tweens.length; - - for ( ; index < length; index++ ) { - animation.tweens[ index ].run( percent ); - } - - deferred.notifyWith( elem, [ animation, percent, remaining ] ); - - if ( percent < 1 && length ) { - return remaining; - } else { - deferred.resolveWith( elem, [ animation ] ); - return false; - } - }, - animation = deferred.promise( { - elem: elem, - props: jQuery.extend( {}, properties ), - opts: jQuery.extend( true, { - specialEasing: {}, - easing: jQuery.easing._default - }, options ), - originalProperties: properties, - originalOptions: options, - startTime: fxNow || createFxNow(), - duration: options.duration, - tweens: [], - createTween: function( prop, end ) { - var tween = jQuery.Tween( elem, animation.opts, prop, end, - animation.opts.specialEasing[ prop ] || animation.opts.easing ); - animation.tweens.push( tween ); - return tween; - }, - stop: function( gotoEnd ) { - var index = 0, - - // If we are going to the end, we want to run all the tweens - // otherwise we skip this part - length = gotoEnd ? animation.tweens.length : 0; - if ( stopped ) { - return this; - } - stopped = true; - for ( ; index < length; index++ ) { - animation.tweens[ index ].run( 1 ); - } - - // Resolve when we played the last frame; otherwise, reject - if ( gotoEnd ) { - deferred.notifyWith( elem, [ animation, 1, 0 ] ); - deferred.resolveWith( elem, [ animation, gotoEnd ] ); - } else { - deferred.rejectWith( elem, [ animation, gotoEnd ] ); - } - return this; - } - } ), - props = animation.props; - - propFilter( props, animation.opts.specialEasing ); - - for ( ; index < length; index++ ) { - result = Animation.prefilters[ index ].call( animation, elem, props, animation.opts ); - if ( result ) { - if ( jQuery.isFunction( result.stop ) ) { - jQuery._queueHooks( animation.elem, animation.opts.queue ).stop = - jQuery.proxy( result.stop, result ); - } - return result; - } - } - - jQuery.map( props, createTween, animation ); - - if ( jQuery.isFunction( animation.opts.start ) ) { - animation.opts.start.call( elem, animation ); - } - - jQuery.fx.timer( - jQuery.extend( tick, { - elem: elem, - anim: animation, - queue: animation.opts.queue - } ) - ); - - // attach callbacks from options - return animation.progress( animation.opts.progress ) - .done( animation.opts.done, animation.opts.complete ) - .fail( animation.opts.fail ) - .always( animation.opts.always ); -} - -jQuery.Animation = jQuery.extend( Animation, { - - tweeners: { - "*": [ function( prop, value ) { - var tween = this.createTween( prop, value ); - adjustCSS( tween.elem, prop, rcssNum.exec( value ), tween ); - return tween; - } ] - }, - - tweener: function( props, callback ) { - if ( jQuery.isFunction( props ) ) { - callback = props; - props = [ "*" ]; - } else { - props = props.match( rnothtmlwhite ); - } - - var prop, - index = 0, - length = props.length; - - for ( ; index < length; index++ ) { - prop = props[ index ]; - Animation.tweeners[ prop ] = Animation.tweeners[ prop ] || []; - Animation.tweeners[ prop ].unshift( callback ); - } - }, - - prefilters: [ defaultPrefilter ], - - prefilter: function( callback, prepend ) { - if ( prepend ) { - Animation.prefilters.unshift( callback ); - } else { - Animation.prefilters.push( callback ); - } - } -} ); - -jQuery.speed = function( speed, easing, fn ) { - var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : { - complete: fn || !fn && easing || - jQuery.isFunction( speed ) && speed, - duration: speed, - easing: fn && easing || easing && !jQuery.isFunction( easing ) && easing - }; - - // Go to the end state if fx are off or if document is hidden - if ( jQuery.fx.off || document.hidden ) { - opt.duration = 0; - - } else { - if ( typeof opt.duration !== "number" ) { - if ( opt.duration in jQuery.fx.speeds ) { - opt.duration = jQuery.fx.speeds[ opt.duration ]; - - } else { - opt.duration = jQuery.fx.speeds._default; - } - } - } - - // Normalize opt.queue - true/undefined/null -> "fx" - if ( opt.queue == null || opt.queue === true ) { - opt.queue = "fx"; - } - - // Queueing - opt.old = opt.complete; - - opt.complete = function() { - if ( jQuery.isFunction( opt.old ) ) { - opt.old.call( this ); - } - - if ( opt.queue ) { - jQuery.dequeue( this, opt.queue ); - } - }; - - return opt; -}; - -jQuery.fn.extend( { - fadeTo: function( speed, to, easing, callback ) { - - // Show any hidden elements after setting opacity to 0 - return this.filter( isHiddenWithinTree ).css( "opacity", 0 ).show() - - // Animate to the value specified - .end().animate( { opacity: to }, speed, easing, callback ); - }, - animate: function( prop, speed, easing, callback ) { - var empty = jQuery.isEmptyObject( prop ), - optall = jQuery.speed( speed, easing, callback ), - doAnimation = function() { - - // Operate on a copy of prop so per-property easing won't be lost - var anim = Animation( this, jQuery.extend( {}, prop ), optall ); - - // Empty animations, or finishing resolves immediately - if ( empty || dataPriv.get( this, "finish" ) ) { - anim.stop( true ); - } - }; - doAnimation.finish = doAnimation; - - return empty || optall.queue === false ? - this.each( doAnimation ) : - this.queue( optall.queue, doAnimation ); - }, - stop: function( type, clearQueue, gotoEnd ) { - var stopQueue = function( hooks ) { - var stop = hooks.stop; - delete hooks.stop; - stop( gotoEnd ); - }; - - if ( typeof type !== "string" ) { - gotoEnd = clearQueue; - clearQueue = type; - type = undefined; - } - if ( clearQueue && type !== false ) { - this.queue( type || "fx", [] ); - } - - return this.each( function() { - var dequeue = true, - index = type != null && type + "queueHooks", - timers = jQuery.timers, - data = dataPriv.get( this ); - - if ( index ) { - if ( data[ index ] && data[ index ].stop ) { - stopQueue( data[ index ] ); - } - } else { - for ( index in data ) { - if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) { - stopQueue( data[ index ] ); - } - } - } - - for ( index = timers.length; index--; ) { - if ( timers[ index ].elem === this && - ( type == null || timers[ index ].queue === type ) ) { - - timers[ index ].anim.stop( gotoEnd ); - dequeue = false; - timers.splice( index, 1 ); - } - } - - // Start the next in the queue if the last step wasn't forced. - // Timers currently will call their complete callbacks, which - // will dequeue but only if they were gotoEnd. - if ( dequeue || !gotoEnd ) { - jQuery.dequeue( this, type ); - } - } ); - }, - finish: function( type ) { - if ( type !== false ) { - type = type || "fx"; - } - return this.each( function() { - var index, - data = dataPriv.get( this ), - queue = data[ type + "queue" ], - hooks = data[ type + "queueHooks" ], - timers = jQuery.timers, - length = queue ? queue.length : 0; - - // Enable finishing flag on private data - data.finish = true; - - // Empty the queue first - jQuery.queue( this, type, [] ); - - if ( hooks && hooks.stop ) { - hooks.stop.call( this, true ); - } - - // Look for any active animations, and finish them - for ( index = timers.length; index--; ) { - if ( timers[ index ].elem === this && timers[ index ].queue === type ) { - timers[ index ].anim.stop( true ); - timers.splice( index, 1 ); - } - } - - // Look for any animations in the old queue and finish them - for ( index = 0; index < length; index++ ) { - if ( queue[ index ] && queue[ index ].finish ) { - queue[ index ].finish.call( this ); - } - } - - // Turn off finishing flag - delete data.finish; - } ); - } -} ); - -jQuery.each( [ "toggle", "show", "hide" ], function( i, name ) { - var cssFn = jQuery.fn[ name ]; - jQuery.fn[ name ] = function( speed, easing, callback ) { - return speed == null || typeof speed === "boolean" ? - cssFn.apply( this, arguments ) : - this.animate( genFx( name, true ), speed, easing, callback ); - }; -} ); - -// Generate shortcuts for custom animations -jQuery.each( { - slideDown: genFx( "show" ), - slideUp: genFx( "hide" ), - slideToggle: genFx( "toggle" ), - fadeIn: { opacity: "show" }, - fadeOut: { opacity: "hide" }, - fadeToggle: { opacity: "toggle" } -}, function( name, props ) { - jQuery.fn[ name ] = function( speed, easing, callback ) { - return this.animate( props, speed, easing, callback ); - }; -} ); - -jQuery.timers = []; -jQuery.fx.tick = function() { - var timer, - i = 0, - timers = jQuery.timers; - - fxNow = jQuery.now(); - - for ( ; i < timers.length; i++ ) { - timer = timers[ i ]; - - // Checks the timer has not already been removed - if ( !timer() && timers[ i ] === timer ) { - timers.splice( i--, 1 ); - } - } - - if ( !timers.length ) { - jQuery.fx.stop(); - } - fxNow = undefined; -}; - -jQuery.fx.timer = function( timer ) { - jQuery.timers.push( timer ); - if ( timer() ) { - jQuery.fx.start(); - } else { - jQuery.timers.pop(); - } -}; - -jQuery.fx.interval = 13; -jQuery.fx.start = function() { - if ( !timerId ) { - timerId = window.requestAnimationFrame ? - window.requestAnimationFrame( raf ) : - window.setInterval( jQuery.fx.tick, jQuery.fx.interval ); - } -}; - -jQuery.fx.stop = function() { - if ( window.cancelAnimationFrame ) { - window.cancelAnimationFrame( timerId ); - } else { - window.clearInterval( timerId ); - } - - timerId = null; -}; - -jQuery.fx.speeds = { - slow: 600, - fast: 200, - - // Default speed - _default: 400 -}; - - -// Based off of the plugin by Clint Helfers, with permission. -// https://web.archive.org/web/20100324014747/http://blindsignals.com/index.php/2009/07/jquery-delay/ -jQuery.fn.delay = function( time, type ) { - time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; - type = type || "fx"; - - return this.queue( type, function( next, hooks ) { - var timeout = window.setTimeout( next, time ); - hooks.stop = function() { - window.clearTimeout( timeout ); - }; - } ); -}; - - -( function() { - var input = document.createElement( "input" ), - select = document.createElement( "select" ), - opt = select.appendChild( document.createElement( "option" ) ); - - input.type = "checkbox"; - - // Support: Android <=4.3 only - // Default value for a checkbox should be "on" - support.checkOn = input.value !== ""; - - // Support: IE <=11 only - // Must access selectedIndex to make default options select - support.optSelected = opt.selected; - - // Support: IE <=11 only - // An input loses its value after becoming a radio - input = document.createElement( "input" ); - input.value = "t"; - input.type = "radio"; - support.radioValue = input.value === "t"; -} )(); - - -var boolHook, - attrHandle = jQuery.expr.attrHandle; - -jQuery.fn.extend( { - attr: function( name, value ) { - return access( this, jQuery.attr, name, value, arguments.length > 1 ); - }, - - removeAttr: function( name ) { - return this.each( function() { - jQuery.removeAttr( this, name ); - } ); - } -} ); - -jQuery.extend( { - attr: function( elem, name, value ) { - var ret, hooks, - nType = elem.nodeType; - - // Don't get/set attributes on text, comment and attribute nodes - if ( nType === 3 || nType === 8 || nType === 2 ) { - return; - } - - // Fallback to prop when attributes are not supported - if ( typeof elem.getAttribute === "undefined" ) { - return jQuery.prop( elem, name, value ); - } - - // Attribute hooks are determined by the lowercase version - // Grab necessary hook if one is defined - if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { - hooks = jQuery.attrHooks[ name.toLowerCase() ] || - ( jQuery.expr.match.bool.test( name ) ? boolHook : undefined ); - } - - if ( value !== undefined ) { - if ( value === null ) { - jQuery.removeAttr( elem, name ); - return; - } - - if ( hooks && "set" in hooks && - ( ret = hooks.set( elem, value, name ) ) !== undefined ) { - return ret; - } - - elem.setAttribute( name, value + "" ); - return value; - } - - if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { - return ret; - } - - ret = jQuery.find.attr( elem, name ); - - // Non-existent attributes return null, we normalize to undefined - return ret == null ? undefined : ret; - }, - - attrHooks: { - type: { - set: function( elem, value ) { - if ( !support.radioValue && value === "radio" && - jQuery.nodeName( elem, "input" ) ) { - var val = elem.value; - elem.setAttribute( "type", value ); - if ( val ) { - elem.value = val; - } - return value; - } - } - } - }, - - removeAttr: function( elem, value ) { - var name, - i = 0, - - // Attribute names can contain non-HTML whitespace characters - // https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 - attrNames = value && value.match( rnothtmlwhite ); - - if ( attrNames && elem.nodeType === 1 ) { - while ( ( name = attrNames[ i++ ] ) ) { - elem.removeAttribute( name ); - } - } - } -} ); - -// Hooks for boolean attributes -boolHook = { - set: function( elem, value, name ) { - if ( value === false ) { - - // Remove boolean attributes when set to false - jQuery.removeAttr( elem, name ); - } else { - elem.setAttribute( name, name ); - } - return name; - } -}; - -jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( i, name ) { - var getter = attrHandle[ name ] || jQuery.find.attr; - - attrHandle[ name ] = function( elem, name, isXML ) { - var ret, handle, - lowercaseName = name.toLowerCase(); - - if ( !isXML ) { - - // Avoid an infinite loop by temporarily removing this function from the getter - handle = attrHandle[ lowercaseName ]; - attrHandle[ lowercaseName ] = ret; - ret = getter( elem, name, isXML ) != null ? - lowercaseName : - null; - attrHandle[ lowercaseName ] = handle; - } - return ret; - }; -} ); - - - - -var rfocusable = /^(?:input|select|textarea|button)$/i, - rclickable = /^(?:a|area)$/i; - -jQuery.fn.extend( { - prop: function( name, value ) { - return access( this, jQuery.prop, name, value, arguments.length > 1 ); - }, - - removeProp: function( name ) { - return this.each( function() { - delete this[ jQuery.propFix[ name ] || name ]; - } ); - } -} ); - -jQuery.extend( { - prop: function( elem, name, value ) { - var ret, hooks, - nType = elem.nodeType; - - // Don't get/set properties on text, comment and attribute nodes - if ( nType === 3 || nType === 8 || nType === 2 ) { - return; - } - - if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { - - // Fix name and attach hooks - name = jQuery.propFix[ name ] || name; - hooks = jQuery.propHooks[ name ]; - } - - if ( value !== undefined ) { - if ( hooks && "set" in hooks && - ( ret = hooks.set( elem, value, name ) ) !== undefined ) { - return ret; - } - - return ( elem[ name ] = value ); - } - - if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { - return ret; - } - - return elem[ name ]; - }, - - propHooks: { - tabIndex: { - get: function( elem ) { - - // Support: IE <=9 - 11 only - // elem.tabIndex doesn't always return the - // correct value when it hasn't been explicitly set - // https://web.archive.org/web/20141116233347/http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ - // Use proper attribute retrieval(#12072) - var tabindex = jQuery.find.attr( elem, "tabindex" ); - - if ( tabindex ) { - return parseInt( tabindex, 10 ); - } - - if ( - rfocusable.test( elem.nodeName ) || - rclickable.test( elem.nodeName ) && - elem.href - ) { - return 0; - } - - return -1; - } - } - }, - - propFix: { - "for": "htmlFor", - "class": "className" - } -} ); - -// Support: IE <=11 only -// Accessing the selectedIndex property -// forces the browser to respect setting selected -// on the option -// The getter ensures a default option is selected -// when in an optgroup -// eslint rule "no-unused-expressions" is disabled for this code -// since it considers such accessions noop -if ( !support.optSelected ) { - jQuery.propHooks.selected = { - get: function( elem ) { - - /* eslint no-unused-expressions: "off" */ - - var parent = elem.parentNode; - if ( parent && parent.parentNode ) { - parent.parentNode.selectedIndex; - } - return null; - }, - set: function( elem ) { - - /* eslint no-unused-expressions: "off" */ - - var parent = elem.parentNode; - if ( parent ) { - parent.selectedIndex; - - if ( parent.parentNode ) { - parent.parentNode.selectedIndex; - } - } - } - }; -} - -jQuery.each( [ - "tabIndex", - "readOnly", - "maxLength", - "cellSpacing", - "cellPadding", - "rowSpan", - "colSpan", - "useMap", - "frameBorder", - "contentEditable" -], function() { - jQuery.propFix[ this.toLowerCase() ] = this; -} ); - - - - - // Strip and collapse whitespace according to HTML spec - // https://html.spec.whatwg.org/multipage/infrastructure.html#strip-and-collapse-whitespace - function stripAndCollapse( value ) { - var tokens = value.match( rnothtmlwhite ) || []; - return tokens.join( " " ); - } - - -function getClass( elem ) { - return elem.getAttribute && elem.getAttribute( "class" ) || ""; -} - -jQuery.fn.extend( { - addClass: function( value ) { - var classes, elem, cur, curValue, clazz, j, finalValue, - i = 0; - - if ( jQuery.isFunction( value ) ) { - return this.each( function( j ) { - jQuery( this ).addClass( value.call( this, j, getClass( this ) ) ); - } ); - } - - if ( typeof value === "string" && value ) { - classes = value.match( rnothtmlwhite ) || []; - - while ( ( elem = this[ i++ ] ) ) { - curValue = getClass( elem ); - cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); - - if ( cur ) { - j = 0; - while ( ( clazz = classes[ j++ ] ) ) { - if ( cur.indexOf( " " + clazz + " " ) < 0 ) { - cur += clazz + " "; - } - } - - // Only assign if different to avoid unneeded rendering. - finalValue = stripAndCollapse( cur ); - if ( curValue !== finalValue ) { - elem.setAttribute( "class", finalValue ); - } - } - } - } - - return this; - }, - - removeClass: function( value ) { - var classes, elem, cur, curValue, clazz, j, finalValue, - i = 0; - - if ( jQuery.isFunction( value ) ) { - return this.each( function( j ) { - jQuery( this ).removeClass( value.call( this, j, getClass( this ) ) ); - } ); - } - - if ( !arguments.length ) { - return this.attr( "class", "" ); - } - - if ( typeof value === "string" && value ) { - classes = value.match( rnothtmlwhite ) || []; - - while ( ( elem = this[ i++ ] ) ) { - curValue = getClass( elem ); - - // This expression is here for better compressibility (see addClass) - cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); - - if ( cur ) { - j = 0; - while ( ( clazz = classes[ j++ ] ) ) { - - // Remove *all* instances - while ( cur.indexOf( " " + clazz + " " ) > -1 ) { - cur = cur.replace( " " + clazz + " ", " " ); - } - } - - // Only assign if different to avoid unneeded rendering. - finalValue = stripAndCollapse( cur ); - if ( curValue !== finalValue ) { - elem.setAttribute( "class", finalValue ); - } - } - } - } - - return this; - }, - - toggleClass: function( value, stateVal ) { - var type = typeof value; - - if ( typeof stateVal === "boolean" && type === "string" ) { - return stateVal ? this.addClass( value ) : this.removeClass( value ); - } - - if ( jQuery.isFunction( value ) ) { - return this.each( function( i ) { - jQuery( this ).toggleClass( - value.call( this, i, getClass( this ), stateVal ), - stateVal - ); - } ); - } - - return this.each( function() { - var className, i, self, classNames; - - if ( type === "string" ) { - - // Toggle individual class names - i = 0; - self = jQuery( this ); - classNames = value.match( rnothtmlwhite ) || []; - - while ( ( className = classNames[ i++ ] ) ) { - - // Check each className given, space separated list - if ( self.hasClass( className ) ) { - self.removeClass( className ); - } else { - self.addClass( className ); - } - } - - // Toggle whole class name - } else if ( value === undefined || type === "boolean" ) { - className = getClass( this ); - if ( className ) { - - // Store className if set - dataPriv.set( this, "__className__", className ); - } - - // If the element has a class name or if we're passed `false`, - // then remove the whole classname (if there was one, the above saved it). - // Otherwise bring back whatever was previously saved (if anything), - // falling back to the empty string if nothing was stored. - if ( this.setAttribute ) { - this.setAttribute( "class", - className || value === false ? - "" : - dataPriv.get( this, "__className__" ) || "" - ); - } - } - } ); - }, - - hasClass: function( selector ) { - var className, elem, - i = 0; - - className = " " + selector + " "; - while ( ( elem = this[ i++ ] ) ) { - if ( elem.nodeType === 1 && - ( " " + stripAndCollapse( getClass( elem ) ) + " " ).indexOf( className ) > -1 ) { - return true; - } - } - - return false; - } -} ); - - - - -var rreturn = /\r/g; - -jQuery.fn.extend( { - val: function( value ) { - var hooks, ret, isFunction, - elem = this[ 0 ]; - - if ( !arguments.length ) { - if ( elem ) { - hooks = jQuery.valHooks[ elem.type ] || - jQuery.valHooks[ elem.nodeName.toLowerCase() ]; - - if ( hooks && - "get" in hooks && - ( ret = hooks.get( elem, "value" ) ) !== undefined - ) { - return ret; - } - - ret = elem.value; - - // Handle most common string cases - if ( typeof ret === "string" ) { - return ret.replace( rreturn, "" ); - } - - // Handle cases where value is null/undef or number - return ret == null ? "" : ret; - } - - return; - } - - isFunction = jQuery.isFunction( value ); - - return this.each( function( i ) { - var val; - - if ( this.nodeType !== 1 ) { - return; - } - - if ( isFunction ) { - val = value.call( this, i, jQuery( this ).val() ); - } else { - val = value; - } - - // Treat null/undefined as ""; convert numbers to string - if ( val == null ) { - val = ""; - - } else if ( typeof val === "number" ) { - val += ""; - - } else if ( jQuery.isArray( val ) ) { - val = jQuery.map( val, function( value ) { - return value == null ? "" : value + ""; - } ); - } - - hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; - - // If set returns undefined, fall back to normal setting - if ( !hooks || !( "set" in hooks ) || hooks.set( this, val, "value" ) === undefined ) { - this.value = val; - } - } ); - } -} ); - -jQuery.extend( { - valHooks: { - option: { - get: function( elem ) { - - var val = jQuery.find.attr( elem, "value" ); - return val != null ? - val : - - // Support: IE <=10 - 11 only - // option.text throws exceptions (#14686, #14858) - // Strip and collapse whitespace - // https://html.spec.whatwg.org/#strip-and-collapse-whitespace - stripAndCollapse( jQuery.text( elem ) ); - } - }, - select: { - get: function( elem ) { - var value, option, i, - options = elem.options, - index = elem.selectedIndex, - one = elem.type === "select-one", - values = one ? null : [], - max = one ? index + 1 : options.length; - - if ( index < 0 ) { - i = max; - - } else { - i = one ? index : 0; - } - - // Loop through all the selected options - for ( ; i < max; i++ ) { - option = options[ i ]; - - // Support: IE <=9 only - // IE8-9 doesn't update selected after form reset (#2551) - if ( ( option.selected || i === index ) && - - // Don't return options that are disabled or in a disabled optgroup - !option.disabled && - ( !option.parentNode.disabled || - !jQuery.nodeName( option.parentNode, "optgroup" ) ) ) { - - // Get the specific value for the option - value = jQuery( option ).val(); - - // We don't need an array for one selects - if ( one ) { - return value; - } - - // Multi-Selects return an array - values.push( value ); - } - } - - return values; - }, - - set: function( elem, value ) { - var optionSet, option, - options = elem.options, - values = jQuery.makeArray( value ), - i = options.length; - - while ( i-- ) { - option = options[ i ]; - - /* eslint-disable no-cond-assign */ - - if ( option.selected = - jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1 - ) { - optionSet = true; - } - - /* eslint-enable no-cond-assign */ - } - - // Force browsers to behave consistently when non-matching value is set - if ( !optionSet ) { - elem.selectedIndex = -1; - } - return values; - } - } - } -} ); - -// Radios and checkboxes getter/setter -jQuery.each( [ "radio", "checkbox" ], function() { - jQuery.valHooks[ this ] = { - set: function( elem, value ) { - if ( jQuery.isArray( value ) ) { - return ( elem.checked = jQuery.inArray( jQuery( elem ).val(), value ) > -1 ); - } - } - }; - if ( !support.checkOn ) { - jQuery.valHooks[ this ].get = function( elem ) { - return elem.getAttribute( "value" ) === null ? "on" : elem.value; - }; - } -} ); - - - - -// Return jQuery for attributes-only inclusion - - -var rfocusMorph = /^(?:focusinfocus|focusoutblur)$/; - -jQuery.extend( jQuery.event, { - - trigger: function( event, data, elem, onlyHandlers ) { - - var i, cur, tmp, bubbleType, ontype, handle, special, - eventPath = [ elem || document ], - type = hasOwn.call( event, "type" ) ? event.type : event, - namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : []; - - cur = tmp = elem = elem || document; - - // Don't do events on text and comment nodes - if ( elem.nodeType === 3 || elem.nodeType === 8 ) { - return; - } - - // focus/blur morphs to focusin/out; ensure we're not firing them right now - if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { - return; - } - - if ( type.indexOf( "." ) > -1 ) { - - // Namespaced trigger; create a regexp to match event type in handle() - namespaces = type.split( "." ); - type = namespaces.shift(); - namespaces.sort(); - } - ontype = type.indexOf( ":" ) < 0 && "on" + type; - - // Caller can pass in a jQuery.Event object, Object, or just an event type string - event = event[ jQuery.expando ] ? - event : - new jQuery.Event( type, typeof event === "object" && event ); - - // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) - event.isTrigger = onlyHandlers ? 2 : 3; - event.namespace = namespaces.join( "." ); - event.rnamespace = event.namespace ? - new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ) : - null; - - // Clean up the event in case it is being reused - event.result = undefined; - if ( !event.target ) { - event.target = elem; - } - - // Clone any incoming data and prepend the event, creating the handler arg list - data = data == null ? - [ event ] : - jQuery.makeArray( data, [ event ] ); - - // Allow special events to draw outside the lines - special = jQuery.event.special[ type ] || {}; - if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { - return; - } - - // Determine event propagation path in advance, per W3C events spec (#9951) - // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) - if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) { - - bubbleType = special.delegateType || type; - if ( !rfocusMorph.test( bubbleType + type ) ) { - cur = cur.parentNode; - } - for ( ; cur; cur = cur.parentNode ) { - eventPath.push( cur ); - tmp = cur; - } - - // Only add window if we got to document (e.g., not plain obj or detached DOM) - if ( tmp === ( elem.ownerDocument || document ) ) { - eventPath.push( tmp.defaultView || tmp.parentWindow || window ); - } - } - - // Fire handlers on the event path - i = 0; - while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) { - - event.type = i > 1 ? - bubbleType : - special.bindType || type; - - // jQuery handler - handle = ( dataPriv.get( cur, "events" ) || {} )[ event.type ] && - dataPriv.get( cur, "handle" ); - if ( handle ) { - handle.apply( cur, data ); - } - - // Native handler - handle = ontype && cur[ ontype ]; - if ( handle && handle.apply && acceptData( cur ) ) { - event.result = handle.apply( cur, data ); - if ( event.result === false ) { - event.preventDefault(); - } - } - } - event.type = type; - - // If nobody prevented the default action, do it now - if ( !onlyHandlers && !event.isDefaultPrevented() ) { - - if ( ( !special._default || - special._default.apply( eventPath.pop(), data ) === false ) && - acceptData( elem ) ) { - - // Call a native DOM method on the target with the same name as the event. - // Don't do default actions on window, that's where global variables be (#6170) - if ( ontype && jQuery.isFunction( elem[ type ] ) && !jQuery.isWindow( elem ) ) { - - // Don't re-trigger an onFOO event when we call its FOO() method - tmp = elem[ ontype ]; - - if ( tmp ) { - elem[ ontype ] = null; - } - - // Prevent re-triggering of the same event, since we already bubbled it above - jQuery.event.triggered = type; - elem[ type ](); - jQuery.event.triggered = undefined; - - if ( tmp ) { - elem[ ontype ] = tmp; - } - } - } - } - - return event.result; - }, - - // Piggyback on a donor event to simulate a different one - // Used only for `focus(in | out)` events - simulate: function( type, elem, event ) { - var e = jQuery.extend( - new jQuery.Event(), - event, - { - type: type, - isSimulated: true - } - ); - - jQuery.event.trigger( e, null, elem ); - } - -} ); - -jQuery.fn.extend( { - - trigger: function( type, data ) { - return this.each( function() { - jQuery.event.trigger( type, data, this ); - } ); - }, - triggerHandler: function( type, data ) { - var elem = this[ 0 ]; - if ( elem ) { - return jQuery.event.trigger( type, data, elem, true ); - } - } -} ); - - -jQuery.each( ( "blur focus focusin focusout resize scroll click dblclick " + - "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " + - "change select submit keydown keypress keyup contextmenu" ).split( " " ), - function( i, name ) { - - // Handle event binding - jQuery.fn[ name ] = function( data, fn ) { - return arguments.length > 0 ? - this.on( name, null, data, fn ) : - this.trigger( name ); - }; -} ); - -jQuery.fn.extend( { - hover: function( fnOver, fnOut ) { - return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver ); - } -} ); - - - - -support.focusin = "onfocusin" in window; - - -// Support: Firefox <=44 -// Firefox doesn't have focus(in | out) events -// Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787 -// -// Support: Chrome <=48 - 49, Safari <=9.0 - 9.1 -// focus(in | out) events fire after focus & blur events, -// which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order -// Related ticket - https://bugs.chromium.org/p/chromium/issues/detail?id=449857 -if ( !support.focusin ) { - jQuery.each( { focus: "focusin", blur: "focusout" }, function( orig, fix ) { - - // Attach a single capturing handler on the document while someone wants focusin/focusout - var handler = function( event ) { - jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ) ); - }; - - jQuery.event.special[ fix ] = { - setup: function() { - var doc = this.ownerDocument || this, - attaches = dataPriv.access( doc, fix ); - - if ( !attaches ) { - doc.addEventListener( orig, handler, true ); - } - dataPriv.access( doc, fix, ( attaches || 0 ) + 1 ); - }, - teardown: function() { - var doc = this.ownerDocument || this, - attaches = dataPriv.access( doc, fix ) - 1; - - if ( !attaches ) { - doc.removeEventListener( orig, handler, true ); - dataPriv.remove( doc, fix ); - - } else { - dataPriv.access( doc, fix, attaches ); - } - } - }; - } ); -} -var location = window.location; - -var nonce = jQuery.now(); - -var rquery = ( /\?/ ); - - - -// Cross-browser xml parsing -jQuery.parseXML = function( data ) { - var xml; - if ( !data || typeof data !== "string" ) { - return null; - } - - // Support: IE 9 - 11 only - // IE throws on parseFromString with invalid input. - try { - xml = ( new window.DOMParser() ).parseFromString( data, "text/xml" ); - } catch ( e ) { - xml = undefined; - } - - if ( !xml || xml.getElementsByTagName( "parsererror" ).length ) { - jQuery.error( "Invalid XML: " + data ); - } - return xml; -}; - - -var - rbracket = /\[\]$/, - rCRLF = /\r?\n/g, - rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i, - rsubmittable = /^(?:input|select|textarea|keygen)/i; - -function buildParams( prefix, obj, traditional, add ) { - var name; - - if ( jQuery.isArray( obj ) ) { - - // Serialize array item. - jQuery.each( obj, function( i, v ) { - if ( traditional || rbracket.test( prefix ) ) { - - // Treat each array item as a scalar. - add( prefix, v ); - - } else { - - // Item is non-scalar (array or object), encode its numeric index. - buildParams( - prefix + "[" + ( typeof v === "object" && v != null ? i : "" ) + "]", - v, - traditional, - add - ); - } - } ); - - } else if ( !traditional && jQuery.type( obj ) === "object" ) { - - // Serialize object item. - for ( name in obj ) { - buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add ); - } - - } else { - - // Serialize scalar item. - add( prefix, obj ); - } -} - -// Serialize an array of form elements or a set of -// key/values into a query string -jQuery.param = function( a, traditional ) { - var prefix, - s = [], - add = function( key, valueOrFunction ) { - - // If value is a function, invoke it and use its return value - var value = jQuery.isFunction( valueOrFunction ) ? - valueOrFunction() : - valueOrFunction; - - s[ s.length ] = encodeURIComponent( key ) + "=" + - encodeURIComponent( value == null ? "" : value ); - }; - - // If an array was passed in, assume that it is an array of form elements. - if ( jQuery.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) { - - // Serialize the form elements - jQuery.each( a, function() { - add( this.name, this.value ); - } ); - - } else { - - // If traditional, encode the "old" way (the way 1.3.2 or older - // did it), otherwise encode params recursively. - for ( prefix in a ) { - buildParams( prefix, a[ prefix ], traditional, add ); - } - } - - // Return the resulting serialization - return s.join( "&" ); -}; - -jQuery.fn.extend( { - serialize: function() { - return jQuery.param( this.serializeArray() ); - }, - serializeArray: function() { - return this.map( function() { - - // Can add propHook for "elements" to filter or add form elements - var elements = jQuery.prop( this, "elements" ); - return elements ? jQuery.makeArray( elements ) : this; - } ) - .filter( function() { - var type = this.type; - - // Use .is( ":disabled" ) so that fieldset[disabled] works - return this.name && !jQuery( this ).is( ":disabled" ) && - rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) && - ( this.checked || !rcheckableType.test( type ) ); - } ) - .map( function( i, elem ) { - var val = jQuery( this ).val(); - - if ( val == null ) { - return null; - } - - if ( jQuery.isArray( val ) ) { - return jQuery.map( val, function( val ) { - return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; - } ); - } - - return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; - } ).get(); - } -} ); - - -var - r20 = /%20/g, - rhash = /#.*$/, - rantiCache = /([?&])_=[^&]*/, - rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg, - - // #7653, #8125, #8152: local protocol detection - rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/, - rnoContent = /^(?:GET|HEAD)$/, - rprotocol = /^\/\//, - - /* Prefilters - * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example) - * 2) These are called: - * - BEFORE asking for a transport - * - AFTER param serialization (s.data is a string if s.processData is true) - * 3) key is the dataType - * 4) the catchall symbol "*" can be used - * 5) execution will start with transport dataType and THEN continue down to "*" if needed - */ - prefilters = {}, - - /* Transports bindings - * 1) key is the dataType - * 2) the catchall symbol "*" can be used - * 3) selection will start with transport dataType and THEN go to "*" if needed - */ - transports = {}, - - // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression - allTypes = "*/".concat( "*" ), - - // Anchor tag for parsing the document origin - originAnchor = document.createElement( "a" ); - originAnchor.href = location.href; - -// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport -function addToPrefiltersOrTransports( structure ) { - - // dataTypeExpression is optional and defaults to "*" - return function( dataTypeExpression, func ) { - - if ( typeof dataTypeExpression !== "string" ) { - func = dataTypeExpression; - dataTypeExpression = "*"; - } - - var dataType, - i = 0, - dataTypes = dataTypeExpression.toLowerCase().match( rnothtmlwhite ) || []; - - if ( jQuery.isFunction( func ) ) { - - // For each dataType in the dataTypeExpression - while ( ( dataType = dataTypes[ i++ ] ) ) { - - // Prepend if requested - if ( dataType[ 0 ] === "+" ) { - dataType = dataType.slice( 1 ) || "*"; - ( structure[ dataType ] = structure[ dataType ] || [] ).unshift( func ); - - // Otherwise append - } else { - ( structure[ dataType ] = structure[ dataType ] || [] ).push( func ); - } - } - } - }; -} - -// Base inspection function for prefilters and transports -function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) { - - var inspected = {}, - seekingTransport = ( structure === transports ); - - function inspect( dataType ) { - var selected; - inspected[ dataType ] = true; - jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) { - var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR ); - if ( typeof dataTypeOrTransport === "string" && - !seekingTransport && !inspected[ dataTypeOrTransport ] ) { - - options.dataTypes.unshift( dataTypeOrTransport ); - inspect( dataTypeOrTransport ); - return false; - } else if ( seekingTransport ) { - return !( selected = dataTypeOrTransport ); - } - } ); - return selected; - } - - return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" ); -} - -// A special extend for ajax options -// that takes "flat" options (not to be deep extended) -// Fixes #9887 -function ajaxExtend( target, src ) { - var key, deep, - flatOptions = jQuery.ajaxSettings.flatOptions || {}; - - for ( key in src ) { - if ( src[ key ] !== undefined ) { - ( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ]; - } - } - if ( deep ) { - jQuery.extend( true, target, deep ); - } - - return target; -} - -/* Handles responses to an ajax request: - * - finds the right dataType (mediates between content-type and expected dataType) - * - returns the corresponding response - */ -function ajaxHandleResponses( s, jqXHR, responses ) { - - var ct, type, finalDataType, firstDataType, - contents = s.contents, - dataTypes = s.dataTypes; - - // Remove auto dataType and get content-type in the process - while ( dataTypes[ 0 ] === "*" ) { - dataTypes.shift(); - if ( ct === undefined ) { - ct = s.mimeType || jqXHR.getResponseHeader( "Content-Type" ); - } - } - - // Check if we're dealing with a known content-type - if ( ct ) { - for ( type in contents ) { - if ( contents[ type ] && contents[ type ].test( ct ) ) { - dataTypes.unshift( type ); - break; - } - } - } - - // Check to see if we have a response for the expected dataType - if ( dataTypes[ 0 ] in responses ) { - finalDataType = dataTypes[ 0 ]; - } else { - - // Try convertible dataTypes - for ( type in responses ) { - if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[ 0 ] ] ) { - finalDataType = type; - break; - } - if ( !firstDataType ) { - firstDataType = type; - } - } - - // Or just use first one - finalDataType = finalDataType || firstDataType; - } - - // If we found a dataType - // We add the dataType to the list if needed - // and return the corresponding response - if ( finalDataType ) { - if ( finalDataType !== dataTypes[ 0 ] ) { - dataTypes.unshift( finalDataType ); - } - return responses[ finalDataType ]; - } -} - -/* Chain conversions given the request and the original response - * Also sets the responseXXX fields on the jqXHR instance - */ -function ajaxConvert( s, response, jqXHR, isSuccess ) { - var conv2, current, conv, tmp, prev, - converters = {}, - - // Work with a copy of dataTypes in case we need to modify it for conversion - dataTypes = s.dataTypes.slice(); - - // Create converters map with lowercased keys - if ( dataTypes[ 1 ] ) { - for ( conv in s.converters ) { - converters[ conv.toLowerCase() ] = s.converters[ conv ]; - } - } - - current = dataTypes.shift(); - - // Convert to each sequential dataType - while ( current ) { - - if ( s.responseFields[ current ] ) { - jqXHR[ s.responseFields[ current ] ] = response; - } - - // Apply the dataFilter if provided - if ( !prev && isSuccess && s.dataFilter ) { - response = s.dataFilter( response, s.dataType ); - } - - prev = current; - current = dataTypes.shift(); - - if ( current ) { - - // There's only work to do if current dataType is non-auto - if ( current === "*" ) { - - current = prev; - - // Convert response if prev dataType is non-auto and differs from current - } else if ( prev !== "*" && prev !== current ) { - - // Seek a direct converter - conv = converters[ prev + " " + current ] || converters[ "* " + current ]; - - // If none found, seek a pair - if ( !conv ) { - for ( conv2 in converters ) { - - // If conv2 outputs current - tmp = conv2.split( " " ); - if ( tmp[ 1 ] === current ) { - - // If prev can be converted to accepted input - conv = converters[ prev + " " + tmp[ 0 ] ] || - converters[ "* " + tmp[ 0 ] ]; - if ( conv ) { - - // Condense equivalence converters - if ( conv === true ) { - conv = converters[ conv2 ]; - - // Otherwise, insert the intermediate dataType - } else if ( converters[ conv2 ] !== true ) { - current = tmp[ 0 ]; - dataTypes.unshift( tmp[ 1 ] ); - } - break; - } - } - } - } - - // Apply converter (if not an equivalence) - if ( conv !== true ) { - - // Unless errors are allowed to bubble, catch and return them - if ( conv && s.throws ) { - response = conv( response ); - } else { - try { - response = conv( response ); - } catch ( e ) { - return { - state: "parsererror", - error: conv ? e : "No conversion from " + prev + " to " + current - }; - } - } - } - } - } - } - - return { state: "success", data: response }; -} - -jQuery.extend( { - - // Counter for holding the number of active queries - active: 0, - - // Last-Modified header cache for next request - lastModified: {}, - etag: {}, - - ajaxSettings: { - url: location.href, - type: "GET", - isLocal: rlocalProtocol.test( location.protocol ), - global: true, - processData: true, - async: true, - contentType: "application/x-www-form-urlencoded; charset=UTF-8", - - /* - timeout: 0, - data: null, - dataType: null, - username: null, - password: null, - cache: null, - throws: false, - traditional: false, - headers: {}, - */ - - accepts: { - "*": allTypes, - text: "text/plain", - html: "text/html", - xml: "application/xml, text/xml", - json: "application/json, text/javascript" - }, - - contents: { - xml: /\bxml\b/, - html: /\bhtml/, - json: /\bjson\b/ - }, - - responseFields: { - xml: "responseXML", - text: "responseText", - json: "responseJSON" - }, - - // Data converters - // Keys separate source (or catchall "*") and destination types with a single space - converters: { - - // Convert anything to text - "* text": String, - - // Text to html (true = no transformation) - "text html": true, - - // Evaluate text as a json expression - "text json": JSON.parse, - - // Parse text as xml - "text xml": jQuery.parseXML - }, - - // For options that shouldn't be deep extended: - // you can add your own custom options here if - // and when you create one that shouldn't be - // deep extended (see ajaxExtend) - flatOptions: { - url: true, - context: true - } - }, - - // Creates a full fledged settings object into target - // with both ajaxSettings and settings fields. - // If target is omitted, writes into ajaxSettings. - ajaxSetup: function( target, settings ) { - return settings ? - - // Building a settings object - ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) : - - // Extending ajaxSettings - ajaxExtend( jQuery.ajaxSettings, target ); - }, - - ajaxPrefilter: addToPrefiltersOrTransports( prefilters ), - ajaxTransport: addToPrefiltersOrTransports( transports ), - - // Main method - ajax: function( url, options ) { - - // If url is an object, simulate pre-1.5 signature - if ( typeof url === "object" ) { - options = url; - url = undefined; - } - - // Force options to be an object - options = options || {}; - - var transport, - - // URL without anti-cache param - cacheURL, - - // Response headers - responseHeadersString, - responseHeaders, - - // timeout handle - timeoutTimer, - - // Url cleanup var - urlAnchor, - - // Request state (becomes false upon send and true upon completion) - completed, - - // To know if global events are to be dispatched - fireGlobals, - - // Loop variable - i, - - // uncached part of the url - uncached, - - // Create the final options object - s = jQuery.ajaxSetup( {}, options ), - - // Callbacks context - callbackContext = s.context || s, - - // Context for global events is callbackContext if it is a DOM node or jQuery collection - globalEventContext = s.context && - ( callbackContext.nodeType || callbackContext.jquery ) ? - jQuery( callbackContext ) : - jQuery.event, - - // Deferreds - deferred = jQuery.Deferred(), - completeDeferred = jQuery.Callbacks( "once memory" ), - - // Status-dependent callbacks - statusCode = s.statusCode || {}, - - // Headers (they are sent all at once) - requestHeaders = {}, - requestHeadersNames = {}, - - // Default abort message - strAbort = "canceled", - - // Fake xhr - jqXHR = { - readyState: 0, - - // Builds headers hashtable if needed - getResponseHeader: function( key ) { - var match; - if ( completed ) { - if ( !responseHeaders ) { - responseHeaders = {}; - while ( ( match = rheaders.exec( responseHeadersString ) ) ) { - responseHeaders[ match[ 1 ].toLowerCase() ] = match[ 2 ]; - } - } - match = responseHeaders[ key.toLowerCase() ]; - } - return match == null ? null : match; - }, - - // Raw string - getAllResponseHeaders: function() { - return completed ? responseHeadersString : null; - }, - - // Caches the header - setRequestHeader: function( name, value ) { - if ( completed == null ) { - name = requestHeadersNames[ name.toLowerCase() ] = - requestHeadersNames[ name.toLowerCase() ] || name; - requestHeaders[ name ] = value; - } - return this; - }, - - // Overrides response content-type header - overrideMimeType: function( type ) { - if ( completed == null ) { - s.mimeType = type; - } - return this; - }, - - // Status-dependent callbacks - statusCode: function( map ) { - var code; - if ( map ) { - if ( completed ) { - - // Execute the appropriate callbacks - jqXHR.always( map[ jqXHR.status ] ); - } else { - - // Lazy-add the new callbacks in a way that preserves old ones - for ( code in map ) { - statusCode[ code ] = [ statusCode[ code ], map[ code ] ]; - } - } - } - return this; - }, - - // Cancel the request - abort: function( statusText ) { - var finalText = statusText || strAbort; - if ( transport ) { - transport.abort( finalText ); - } - done( 0, finalText ); - return this; - } - }; - - // Attach deferreds - deferred.promise( jqXHR ); - - // Add protocol if not provided (prefilters might expect it) - // Handle falsy url in the settings object (#10093: consistency with old signature) - // We also use the url parameter if available - s.url = ( ( url || s.url || location.href ) + "" ) - .replace( rprotocol, location.protocol + "//" ); - - // Alias method option to type as per ticket #12004 - s.type = options.method || options.type || s.method || s.type; - - // Extract dataTypes list - s.dataTypes = ( s.dataType || "*" ).toLowerCase().match( rnothtmlwhite ) || [ "" ]; - - // A cross-domain request is in order when the origin doesn't match the current origin. - if ( s.crossDomain == null ) { - urlAnchor = document.createElement( "a" ); - - // Support: IE <=8 - 11, Edge 12 - 13 - // IE throws exception on accessing the href property if url is malformed, - // e.g. http://example.com:80x/ - try { - urlAnchor.href = s.url; - - // Support: IE <=8 - 11 only - // Anchor's host property isn't correctly set when s.url is relative - urlAnchor.href = urlAnchor.href; - s.crossDomain = originAnchor.protocol + "//" + originAnchor.host !== - urlAnchor.protocol + "//" + urlAnchor.host; - } catch ( e ) { - - // If there is an error parsing the URL, assume it is crossDomain, - // it can be rejected by the transport if it is invalid - s.crossDomain = true; - } - } - - // Convert data if not already a string - if ( s.data && s.processData && typeof s.data !== "string" ) { - s.data = jQuery.param( s.data, s.traditional ); - } - - // Apply prefilters - inspectPrefiltersOrTransports( prefilters, s, options, jqXHR ); - - // If request was aborted inside a prefilter, stop there - if ( completed ) { - return jqXHR; - } - - // We can fire global events as of now if asked to - // Don't fire events if jQuery.event is undefined in an AMD-usage scenario (#15118) - fireGlobals = jQuery.event && s.global; - - // Watch for a new set of requests - if ( fireGlobals && jQuery.active++ === 0 ) { - jQuery.event.trigger( "ajaxStart" ); - } - - // Uppercase the type - s.type = s.type.toUpperCase(); - - // Determine if request has content - s.hasContent = !rnoContent.test( s.type ); - - // Save the URL in case we're toying with the If-Modified-Since - // and/or If-None-Match header later on - // Remove hash to simplify url manipulation - cacheURL = s.url.replace( rhash, "" ); - - // More options handling for requests with no content - if ( !s.hasContent ) { - - // Remember the hash so we can put it back - uncached = s.url.slice( cacheURL.length ); - - // If data is available, append data to url - if ( s.data ) { - cacheURL += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data; - - // #9682: remove data so that it's not used in an eventual retry - delete s.data; - } - - // Add or update anti-cache param if needed - if ( s.cache === false ) { - cacheURL = cacheURL.replace( rantiCache, "$1" ); - uncached = ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ( nonce++ ) + uncached; - } - - // Put hash and anti-cache on the URL that will be requested (gh-1732) - s.url = cacheURL + uncached; - - // Change '%20' to '+' if this is encoded form body content (gh-2658) - } else if ( s.data && s.processData && - ( s.contentType || "" ).indexOf( "application/x-www-form-urlencoded" ) === 0 ) { - s.data = s.data.replace( r20, "+" ); - } - - // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. - if ( s.ifModified ) { - if ( jQuery.lastModified[ cacheURL ] ) { - jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] ); - } - if ( jQuery.etag[ cacheURL ] ) { - jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] ); - } - } - - // Set the correct header, if data is being sent - if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { - jqXHR.setRequestHeader( "Content-Type", s.contentType ); - } - - // Set the Accepts header for the server, depending on the dataType - jqXHR.setRequestHeader( - "Accept", - s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ? - s.accepts[ s.dataTypes[ 0 ] ] + - ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) : - s.accepts[ "*" ] - ); - - // Check for headers option - for ( i in s.headers ) { - jqXHR.setRequestHeader( i, s.headers[ i ] ); - } - - // Allow custom headers/mimetypes and early abort - if ( s.beforeSend && - ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || completed ) ) { - - // Abort if not done already and return - return jqXHR.abort(); - } - - // Aborting is no longer a cancellation - strAbort = "abort"; - - // Install callbacks on deferreds - completeDeferred.add( s.complete ); - jqXHR.done( s.success ); - jqXHR.fail( s.error ); - - // Get transport - transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR ); - - // If no transport, we auto-abort - if ( !transport ) { - done( -1, "No Transport" ); - } else { - jqXHR.readyState = 1; - - // Send global event - if ( fireGlobals ) { - globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] ); - } - - // If request was aborted inside ajaxSend, stop there - if ( completed ) { - return jqXHR; - } - - // Timeout - if ( s.async && s.timeout > 0 ) { - timeoutTimer = window.setTimeout( function() { - jqXHR.abort( "timeout" ); - }, s.timeout ); - } - - try { - completed = false; - transport.send( requestHeaders, done ); - } catch ( e ) { - - // Rethrow post-completion exceptions - if ( completed ) { - throw e; - } - - // Propagate others as results - done( -1, e ); - } - } - - // Callback for when everything is done - function done( status, nativeStatusText, responses, headers ) { - var isSuccess, success, error, response, modified, - statusText = nativeStatusText; - - // Ignore repeat invocations - if ( completed ) { - return; - } - - completed = true; - - // Clear timeout if it exists - if ( timeoutTimer ) { - window.clearTimeout( timeoutTimer ); - } - - // Dereference transport for early garbage collection - // (no matter how long the jqXHR object will be used) - transport = undefined; - - // Cache response headers - responseHeadersString = headers || ""; - - // Set readyState - jqXHR.readyState = status > 0 ? 4 : 0; - - // Determine if successful - isSuccess = status >= 200 && status < 300 || status === 304; - - // Get response data - if ( responses ) { - response = ajaxHandleResponses( s, jqXHR, responses ); - } - - // Convert no matter what (that way responseXXX fields are always set) - response = ajaxConvert( s, response, jqXHR, isSuccess ); - - // If successful, handle type chaining - if ( isSuccess ) { - - // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. - if ( s.ifModified ) { - modified = jqXHR.getResponseHeader( "Last-Modified" ); - if ( modified ) { - jQuery.lastModified[ cacheURL ] = modified; - } - modified = jqXHR.getResponseHeader( "etag" ); - if ( modified ) { - jQuery.etag[ cacheURL ] = modified; - } - } - - // if no content - if ( status === 204 || s.type === "HEAD" ) { - statusText = "nocontent"; - - // if not modified - } else if ( status === 304 ) { - statusText = "notmodified"; - - // If we have data, let's convert it - } else { - statusText = response.state; - success = response.data; - error = response.error; - isSuccess = !error; - } - } else { - - // Extract error from statusText and normalize for non-aborts - error = statusText; - if ( status || !statusText ) { - statusText = "error"; - if ( status < 0 ) { - status = 0; - } - } - } - - // Set data for the fake xhr object - jqXHR.status = status; - jqXHR.statusText = ( nativeStatusText || statusText ) + ""; - - // Success/Error - if ( isSuccess ) { - deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] ); - } else { - deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] ); - } - - // Status-dependent callbacks - jqXHR.statusCode( statusCode ); - statusCode = undefined; - - if ( fireGlobals ) { - globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError", - [ jqXHR, s, isSuccess ? success : error ] ); - } - - // Complete - completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] ); - - if ( fireGlobals ) { - globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] ); - - // Handle the global AJAX counter - if ( !( --jQuery.active ) ) { - jQuery.event.trigger( "ajaxStop" ); - } - } - } - - return jqXHR; - }, - - getJSON: function( url, data, callback ) { - return jQuery.get( url, data, callback, "json" ); - }, - - getScript: function( url, callback ) { - return jQuery.get( url, undefined, callback, "script" ); - } -} ); - -jQuery.each( [ "get", "post" ], function( i, method ) { - jQuery[ method ] = function( url, data, callback, type ) { - - // Shift arguments if data argument was omitted - if ( jQuery.isFunction( data ) ) { - type = type || callback; - callback = data; - data = undefined; - } - - // The url can be an options object (which then must have .url) - return jQuery.ajax( jQuery.extend( { - url: url, - type: method, - dataType: type, - data: data, - success: callback - }, jQuery.isPlainObject( url ) && url ) ); - }; -} ); - - -jQuery._evalUrl = function( url ) { - return jQuery.ajax( { - url: url, - - // Make this explicit, since user can override this through ajaxSetup (#11264) - type: "GET", - dataType: "script", - cache: true, - async: false, - global: false, - "throws": true - } ); -}; - - -jQuery.fn.extend( { - wrapAll: function( html ) { - var wrap; - - if ( this[ 0 ] ) { - if ( jQuery.isFunction( html ) ) { - html = html.call( this[ 0 ] ); - } - - // The elements to wrap the target around - wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true ); - - if ( this[ 0 ].parentNode ) { - wrap.insertBefore( this[ 0 ] ); - } - - wrap.map( function() { - var elem = this; - - while ( elem.firstElementChild ) { - elem = elem.firstElementChild; - } - - return elem; - } ).append( this ); - } - - return this; - }, - - wrapInner: function( html ) { - if ( jQuery.isFunction( html ) ) { - return this.each( function( i ) { - jQuery( this ).wrapInner( html.call( this, i ) ); - } ); - } - - return this.each( function() { - var self = jQuery( this ), - contents = self.contents(); - - if ( contents.length ) { - contents.wrapAll( html ); - - } else { - self.append( html ); - } - } ); - }, - - wrap: function( html ) { - var isFunction = jQuery.isFunction( html ); - - return this.each( function( i ) { - jQuery( this ).wrapAll( isFunction ? html.call( this, i ) : html ); - } ); - }, - - unwrap: function( selector ) { - this.parent( selector ).not( "body" ).each( function() { - jQuery( this ).replaceWith( this.childNodes ); - } ); - return this; - } -} ); - - -jQuery.expr.pseudos.hidden = function( elem ) { - return !jQuery.expr.pseudos.visible( elem ); -}; -jQuery.expr.pseudos.visible = function( elem ) { - return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length ); -}; - - - - -jQuery.ajaxSettings.xhr = function() { - try { - return new window.XMLHttpRequest(); - } catch ( e ) {} -}; - -var xhrSuccessStatus = { - - // File protocol always yields status code 0, assume 200 - 0: 200, - - // Support: IE <=9 only - // #1450: sometimes IE returns 1223 when it should be 204 - 1223: 204 - }, - xhrSupported = jQuery.ajaxSettings.xhr(); - -support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported ); -support.ajax = xhrSupported = !!xhrSupported; - -jQuery.ajaxTransport( function( options ) { - var callback, errorCallback; - - // Cross domain only allowed if supported through XMLHttpRequest - if ( support.cors || xhrSupported && !options.crossDomain ) { - return { - send: function( headers, complete ) { - var i, - xhr = options.xhr(); - - xhr.open( - options.type, - options.url, - options.async, - options.username, - options.password - ); - - // Apply custom fields if provided - if ( options.xhrFields ) { - for ( i in options.xhrFields ) { - xhr[ i ] = options.xhrFields[ i ]; - } - } - - // Override mime type if needed - if ( options.mimeType && xhr.overrideMimeType ) { - xhr.overrideMimeType( options.mimeType ); - } - - // X-Requested-With header - // For cross-domain requests, seeing as conditions for a preflight are - // akin to a jigsaw puzzle, we simply never set it to be sure. - // (it can always be set on a per-request basis or even using ajaxSetup) - // For same-domain requests, won't change header if already provided. - if ( !options.crossDomain && !headers[ "X-Requested-With" ] ) { - headers[ "X-Requested-With" ] = "XMLHttpRequest"; - } - - // Set headers - for ( i in headers ) { - xhr.setRequestHeader( i, headers[ i ] ); - } - - // Callback - callback = function( type ) { - return function() { - if ( callback ) { - callback = errorCallback = xhr.onload = - xhr.onerror = xhr.onabort = xhr.onreadystatechange = null; - - if ( type === "abort" ) { - xhr.abort(); - } else if ( type === "error" ) { - - // Support: IE <=9 only - // On a manual native abort, IE9 throws - // errors on any property access that is not readyState - if ( typeof xhr.status !== "number" ) { - complete( 0, "error" ); - } else { - complete( - - // File: protocol always yields status 0; see #8605, #14207 - xhr.status, - xhr.statusText - ); - } - } else { - complete( - xhrSuccessStatus[ xhr.status ] || xhr.status, - xhr.statusText, - - // Support: IE <=9 only - // IE9 has no XHR2 but throws on binary (trac-11426) - // For XHR2 non-text, let the caller handle it (gh-2498) - ( xhr.responseType || "text" ) !== "text" || - typeof xhr.responseText !== "string" ? - { binary: xhr.response } : - { text: xhr.responseText }, - xhr.getAllResponseHeaders() - ); - } - } - }; - }; - - // Listen to events - xhr.onload = callback(); - errorCallback = xhr.onerror = callback( "error" ); - - // Support: IE 9 only - // Use onreadystatechange to replace onabort - // to handle uncaught aborts - if ( xhr.onabort !== undefined ) { - xhr.onabort = errorCallback; - } else { - xhr.onreadystatechange = function() { - - // Check readyState before timeout as it changes - if ( xhr.readyState === 4 ) { - - // Allow onerror to be called first, - // but that will not handle a native abort - // Also, save errorCallback to a variable - // as xhr.onerror cannot be accessed - window.setTimeout( function() { - if ( callback ) { - errorCallback(); - } - } ); - } - }; - } - - // Create the abort callback - callback = callback( "abort" ); - - try { - - // Do send the request (this may raise an exception) - xhr.send( options.hasContent && options.data || null ); - } catch ( e ) { - - // #14683: Only rethrow if this hasn't been notified as an error yet - if ( callback ) { - throw e; - } - } - }, - - abort: function() { - if ( callback ) { - callback(); - } - } - }; - } -} ); - - - - -// Prevent auto-execution of scripts when no explicit dataType was provided (See gh-2432) -jQuery.ajaxPrefilter( function( s ) { - if ( s.crossDomain ) { - s.contents.script = false; - } -} ); - -// Install script dataType -jQuery.ajaxSetup( { - accepts: { - script: "text/javascript, application/javascript, " + - "application/ecmascript, application/x-ecmascript" - }, - contents: { - script: /\b(?:java|ecma)script\b/ - }, - converters: { - "text script": function( text ) { - jQuery.globalEval( text ); - return text; - } - } -} ); - -// Handle cache's special case and crossDomain -jQuery.ajaxPrefilter( "script", function( s ) { - if ( s.cache === undefined ) { - s.cache = false; - } - if ( s.crossDomain ) { - s.type = "GET"; - } -} ); - -// Bind script tag hack transport -jQuery.ajaxTransport( "script", function( s ) { - - // This transport only deals with cross domain requests - if ( s.crossDomain ) { - var script, callback; - return { - send: function( _, complete ) { - script = jQuery( " -@endsection diff --git a/resources/views/admin/api/new.blade.php b/resources/views/admin/api/new.blade.php deleted file mode 100644 index b5876ee8c9..0000000000 --- a/resources/views/admin/api/new.blade.php +++ /dev/null @@ -1,70 +0,0 @@ -@extends('layouts.admin') - -@section('title') - Application API -@endsection - -@section('content-header') -

    Application APICreate a new application API key.

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

    Select Permissions

    -
    -
    - - @foreach($resources as $resource) - - - - - - - @endforeach -
    {{ str_replace('_', ' ', title_case($resource)) }} - - - - - - - - -
    -
    -
    -
    -
    -
    -
    -
    - - -
    -

    Once you have assigned permissions and created this set of credentials you will be unable to come back and edit it. If you need to make changes down the road you will need to create a new set of credentials.

    -
    - -
    -
    - -
    -@endsection - -@section('footer-scripts') - @parent - -@endsection diff --git a/resources/views/admin/databases/index.blade.php b/resources/views/admin/databases/index.blade.php deleted file mode 100644 index e4c69c5130..0000000000 --- a/resources/views/admin/databases/index.blade.php +++ /dev/null @@ -1,130 +0,0 @@ -@extends('layouts.admin') - -@section('title') - Database Hosts -@endsection - -@section('content-header') -

    Database HostsDatabase hosts that servers can have databases created on.

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

    Host List

    -
    - -
    -
    -
    - - - - - - - - - - - - @foreach ($hosts as $host) - - - - - - - - - - @endforeach - -
    IDNameHostPortUsernameDatabasesNode
    {{ $host->id }}{{ $host->name }}{{ $host->host }}{{ $host->port }}{{ $host->username }}{{ $host->databases_count }} - @if(! is_null($host->node)) - {{ $host->node->name }} - @else - None - @endif -
    -
    -
    -
    -
    - -@endsection - -@section('footer-scripts') - @parent - -@endsection diff --git a/resources/views/admin/databases/view.blade.php b/resources/views/admin/databases/view.blade.php deleted file mode 100644 index e105751a0c..0000000000 --- a/resources/views/admin/databases/view.blade.php +++ /dev/null @@ -1,135 +0,0 @@ -@extends('layouts.admin') - -@section('title') - Database Hosts → View → {{ $host->name }} -@endsection - -@section('content-header') -

    {{ $host->name }}Viewing associated databases and details for this database host.

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

    Host Details

    -
    -
    -
    - - -
    -
    - - -

    The IP address or FQDN that should be used when attempting to connect to this MySQL host from the panel to add new databases.

    -
    -
    - - -

    The port that MySQL is running on for this host.

    -
    -
    - - -

    This setting does nothing other than default to this database host when adding a database to a server on the selected node.

    -
    -
    -
    -
    -
    -
    -
    -

    User Details

    -
    -
    -
    - - -

    The username of an account that has enough permissions to create new users and databases on the system.

    -
    -
    - - -

    The password to the account defined. Leave blank to continue using the assigned password.

    -
    -
    -

    The account defined for this database host must have the WITH GRANT OPTION permission. If the defined account does not have this permission requests to create databases will fail. Do not use the same account details for MySQL that you have defined for this panel.

    -
    - -
    -
    -
    -
    -
    -
    -
    -
    -

    Databases

    -
    -
    - - - - - - - - - - @foreach($databases as $database) - - - - - - @if($database->max_connections != null) - - @else - - @endif - - - @endforeach -
    ServerDatabase NameUsernameConnections FromMax Connections
    {{ $database->getRelation('server')->name }}{{ $database->database }}{{ $database->username }}{{ $database->remote }}{{ $database->max_connections }}Unlimited - - - -
    -
    - @if($databases->hasPages()) - - @endif -
    -
    -
    -@endsection - -@section('footer-scripts') - @parent - -@endsection diff --git a/resources/views/admin/eggs/new.blade.php b/resources/views/admin/eggs/new.blade.php deleted file mode 100644 index 9a0a07849e..0000000000 --- a/resources/views/admin/eggs/new.blade.php +++ /dev/null @@ -1,165 +0,0 @@ -@extends('layouts.admin') - -@section('title') - Nests → New Egg -@endsection - -@section('content-header') -

    New EggCreate a new Egg to assign to servers.

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

    Configuration

    -
    -
    -
    -
    -
    - -
    - -

    Think of a Nest as a category. You can put multiple Eggs in a nest, but consider putting only Eggs that are related to each other in each Nest.

    -
    -
    -
    - - -

    A simple, human-readable name to use as an identifier for this Egg. This is what users will see as their game server type.

    -
    -
    - - -

    A description of this Egg.

    -
    -
    -
    - - -

    - Forces all outgoing network traffic to have its Source IP NATed to the IP of the server's primary allocation IP. - Required for certain games to work properly when the Node has multiple public IP addresses. -
    - - Enabling this option will disable internal networking for any servers using this egg, - causing them to be unable to internally access other servers on the same node. - -

    -
    -
    -
    -
    -
    - - -

    The docker images available to servers using this egg. Enter one per line. Users will be able to select from this list of images if more than one value is provided.

    -
    -
    - - -

    The default startup command that should be used for new servers created with this Egg. You can change this per-server as needed.

    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Process Management

    -
    -
    -
    -
    -
    -

    All fields are required unless you select a separate option from the 'Copy Settings From' dropdown, in which case fields may be left blank to use the values from that option.

    -
    -
    -
    -
    - - -

    If you would like to default to settings from another Egg select it from the dropdown above.

    -
    -
    - - -

    The command that should be sent to server processes to stop them gracefully. If you need to send a SIGINT you should enter ^C here.

    -
    -
    - - -

    This should be a JSON representation of where log files are stored, and whether or not the daemon should be creating custom logs.

    -
    -
    -
    -
    - - -

    This should be a JSON representation of configuration files to modify and what parts should be changed.

    -
    -
    - - -

    This should be a JSON representation of what values the daemon should be looking for when booting a server to determine completion.

    -
    -
    -
    -
    - -
    -
    -
    -
    -@endsection - -@section('footer-scripts') - @parent - {!! Theme::js('vendor/lodash/lodash.js') !!} - -@endsection diff --git a/resources/views/admin/eggs/scripts.blade.php b/resources/views/admin/eggs/scripts.blade.php deleted file mode 100644 index 5bbc9ee3a7..0000000000 --- a/resources/views/admin/eggs/scripts.blade.php +++ /dev/null @@ -1,118 +0,0 @@ -@extends('layouts.admin') - -@section('title') - Nests → Egg: {{ $egg->name }} → Install Script -@endsection - -@section('content-header') -

    {{ $egg->name }}Manage the install script for this Egg.

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

    Install Script

    -
    - @if(! is_null($egg->copyFrom)) -
    -
    - This service option is copying installation scripts and container options from {{ $egg->copyFrom->name }}. Any changes you make to this script will not apply unless you select "None" from the dropdown box below. -
    -
    - @endif -
    -
    {{ $egg->script_install }}
    -
    -
    -
    -
    - - -

    If selected, script above will be ignored and script from selected option will be used in place.

    -
    -
    - - -

    Docker container to use when running this script for the server.

    -
    -
    - - -

    The entrypoint command to use for this script.

    -
    -
    -
    -
    - The following service options rely on this script: - @if(count($relyOnScript) > 0) - @foreach($relyOnScript as $rely) - - {{ $rely->name }}@if(!$loop->last), @endif - - @endforeach - @else - none - @endif -
    -
    -
    - -
    -
    -
    -
    -@endsection - -@section('footer-scripts') - @parent - {!! Theme::js('vendor/ace/ace.js') !!} - {!! Theme::js('vendor/ace/ext-modelist.js') !!} - - -@endsection diff --git a/resources/views/admin/eggs/variables.blade.php b/resources/views/admin/eggs/variables.blade.php deleted file mode 100644 index fbc14781ec..0000000000 --- a/resources/views/admin/eggs/variables.blade.php +++ /dev/null @@ -1,156 +0,0 @@ -@extends('layouts.admin') - -@section('title') - Egg → {{ $egg->name }} → Variables -@endsection - -@section('content-header') -

    {{ $egg->name }}Managing variables for this Egg.

    - -@endsection - -@section('content') - -
    -
    - -
    -
    -
    - @foreach($egg->variables as $variable) -
    -
    -
    -

    {{ $variable->name }}

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

    This variable can be accessed in the startup command by using {{ $variable->env_variable }}.

    -
    -
    -
    - - -
    -
    - - -

    These rules are defined using standard Laravel Framework validation rules.

    -
    -
    - -
    -
    -
    - @endforeach -
    - -@endsection - -@section('footer-scripts') - @parent - -@endsection diff --git a/resources/views/admin/eggs/view.blade.php b/resources/views/admin/eggs/view.blade.php deleted file mode 100644 index b999d2a910..0000000000 --- a/resources/views/admin/eggs/view.blade.php +++ /dev/null @@ -1,206 +0,0 @@ -@extends('layouts.admin') - -@section('title') - Nests → Egg: {{ $egg->name }} -@endsection - -@section('content-header') -

    {{ $egg->name }}{{ str_limit($egg->description, 50) }}

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

    If you would like to replace settings for this Egg by uploading a new JSON file, simply select it here and press "Update Egg". This will not change any existing startup strings or Docker images for existing servers.

    -
    -
    -
    -
    - {!! csrf_field() !!} - -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Configuration

    -
    -
    -
    -
    -
    - - -

    A simple, human-readable name to use as an identifier for this Egg.

    -
    -
    - - -

    This is the globally unique identifier for this Egg which the Daemon uses as an identifier.

    -
    -
    - - -

    The author of this version of the Egg. Uploading a new Egg configuration from a different author will change this.

    -
    -
    - - -

    - The docker images available to servers using this egg. Enter one per line. Users - will be able to select from this list of images if more than one value is provided. - Optionally, a display name may be provided by prefixing the image with the name - followed by a pipe character, and then the image URL. Example: Display Name|ghcr.io/my/egg -

    -
    -
    -
    - force_outgoing_ip) checked @endif /> - -

    - Forces all outgoing network traffic to have its Source IP NATed to the IP of the server's primary allocation IP. - Required for certain games to work properly when the Node has multiple public IP addresses. -
    - - Enabling this option will disable internal networking for any servers using this egg, - causing them to be unable to internally access other servers on the same node. - -

    -
    -
    - -
    -
    -
    - - -

    A description of this Egg that will be displayed throughout the Panel as needed.

    -
    -
    - - -

    The default startup command that should be used for new servers using this Egg.

    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Process Management

    -
    -
    -
    -
    -
    -

    The following configuration options should not be edited unless you understand how this system works. If wrongly modified it is possible for the daemon to break.

    -

    All fields are required unless you select a separate option from the 'Copy Settings From' dropdown, in which case fields may be left blank to use the values from that Egg.

    -
    -
    -
    -
    - - -

    If you would like to default to settings from another Egg select it from the menu above.

    -
    -
    - - -

    The command that should be sent to server processes to stop them gracefully. If you need to send a SIGINT you should enter ^C here.

    -
    -
    - - -

    This should be a JSON representation of where log files are stored, and whether or not the daemon should be creating custom logs.

    -
    -
    -
    -
    - - -

    This should be a JSON representation of configuration files to modify and what parts should be changed.

    -
    -
    - - -

    This should be a JSON representation of what values the daemon should be looking for when booting a server to determine completion.

    -
    -
    -
    -
    - -
    -
    -
    -
    -@endsection - -@section('footer-scripts') - @parent - -@endsection diff --git a/resources/views/admin/index.blade.php b/resources/views/admin/index.blade.php deleted file mode 100644 index ed26a92ab4..0000000000 --- a/resources/views/admin/index.blade.php +++ /dev/null @@ -1,53 +0,0 @@ -@extends('layouts.admin') - -@section('title') - Administration -@endsection - -@section('content-header') -

    Administrative OverviewA quick glance at your system.

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

    System Information

    -
    -
    - @if ($version->isLatestPanel()) - You are running Pterodactyl Panel version {{ config('app.version') }}. Your panel is up-to-date! - @else - Your panel is not up-to-date! The latest version is {{ $version->getPanel() }} and you are currently running version {{ config('app.version') }}. - @endif -
    -
    -
    -
    - -@endsection diff --git a/resources/views/admin/locations/index.blade.php b/resources/views/admin/locations/index.blade.php deleted file mode 100644 index c54ffe0faf..0000000000 --- a/resources/views/admin/locations/index.blade.php +++ /dev/null @@ -1,81 +0,0 @@ -@extends('layouts.admin') - -@section('title') - Locations -@endsection - -@section('content-header') -

    LocationsAll locations that nodes can be assigned to for easier categorization.

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

    Location List

    -
    - -
    -
    -
    - - - - - - - - - - @foreach ($locations as $location) - - - - - - - - @endforeach - -
    IDShort CodeDescriptionNodesServers
    {{ $location->id }}{{ $location->short }}{{ $location->long }}{{ $location->nodes_count }}{{ $location->servers_count }}
    -
    -
    -
    -
    - -@endsection diff --git a/resources/views/admin/locations/view.blade.php b/resources/views/admin/locations/view.blade.php deleted file mode 100644 index f34dcf3f5e..0000000000 --- a/resources/views/admin/locations/view.blade.php +++ /dev/null @@ -1,69 +0,0 @@ -@extends('layouts.admin') - -@section('title') - Locations → View → {{ $location->short }} -@endsection - -@section('content-header') -

    {{ $location->short }}{{ str_limit($location->long, 75) }}

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

    Location Details

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

    Nodes

    -
    -
    - - - - - - - - @foreach($location->nodes as $node) - - - - - - - @endforeach -
    IDNameFQDNServers
    {{ $node->id }}{{ $node->name }}{{ $node->fqdn }}{{ $node->servers->count() }}
    -
    -
    -
    -
    -@endsection diff --git a/resources/views/admin/mounts/index.blade.php b/resources/views/admin/mounts/index.blade.php deleted file mode 100644 index a3b989243e..0000000000 --- a/resources/views/admin/mounts/index.blade.php +++ /dev/null @@ -1,144 +0,0 @@ - -@extends('layouts.admin') - -@section('title') - Mounts -@endsection - -@section('content-header') -

    MountsConfigure and manage additional mount points for servers.

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

    Mount List

    - -
    - -
    -
    - -
    - - - - - - - - - - - - - @foreach ($mounts as $mount) - - - - - - - - - - @endforeach - -
    IDNameSourceTargetEggsNodesServers
    {{ $mount->id }}{{ $mount->name }}{{ $mount->source }}{{ $mount->target }}{{ $mount->eggs_count }}{{ $mount->nodes_count }}{{ $mount->servers_count }}
    -
    -
    -
    -
    - - -@endsection diff --git a/resources/views/admin/mounts/view.blade.php b/resources/views/admin/mounts/view.blade.php deleted file mode 100644 index 96a156d244..0000000000 --- a/resources/views/admin/mounts/view.blade.php +++ /dev/null @@ -1,314 +0,0 @@ - -@extends('layouts.admin') - -@section('title') - Mounts → View → {{ $mount->id }} -@endsection - -@section('content-header') -

    {{ $mount->name }}{{ str_limit($mount->description, 75) }}

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

    Mount Details

    -
    - -
    -
    -
    - - -
    - -
    - - -
    - -
    - - -
    - -
    -
    - - -
    - -
    - - -
    -
    - -
    -
    - - -
    -
    - read_only) checked @endif> - -
    - -
    - read_only) checked @endif> - -
    -
    -
    - -
    - - -
    -
    - user_mountable) checked @endif> - -
    - -
    - user_mountable) checked @endif> - -
    -
    -
    -
    -
    - - -
    -
    -
    - -
    -
    -
    -

    Eggs

    - -
    - -
    -
    - -
    - - - - - - - - @foreach ($mount->eggs as $egg) - - - - - - @endforeach -
    IDName
    {{ $egg->id }}{{ $egg->name }} - -
    -
    -
    - -
    -
    -

    Nodes

    - -
    - -
    -
    - -
    - - - - - - - - - @foreach ($mount->nodes as $node) - - - - - - - @endforeach -
    IDNameFQDN
    {{ $node->id }}{{ $node->name }}{{ $node->fqdn }} - -
    -
    -
    -
    -
    - - - - -@endsection - -@section('footer-scripts') - @parent - - -@endsection diff --git a/resources/views/admin/nests/index.blade.php b/resources/views/admin/nests/index.blade.php deleted file mode 100644 index 4bb6ae7847..0000000000 --- a/resources/views/admin/nests/index.blade.php +++ /dev/null @@ -1,102 +0,0 @@ -@extends('layouts.admin') - -@section('title') - Nests -@endsection - -@section('content-header') -

    NestsAll nests currently available on this system.

    - -@endsection - -@section('content') -
    -
    -
    - Eggs are a powerful feature of Pterodactyl Panel that allow for extreme flexibility and configuration. Please note that while powerful, modifying an egg wrongly can very easily brick your servers and cause more problems. Please avoid editing our default eggs — those provided by support@pterodactyl.io — unless you are absolutely sure of what you are doing. -
    -
    -
    -
    -
    -
    -
    -

    Configured Nests

    - -
    -
    - - - - - - - - - @foreach($nests as $nest) - - - - - - - - @endforeach -
    IDNameDescriptionEggsServers
    {{ $nest->id }}{{ $nest->name }}{{ $nest->description }}{{ $nest->eggs_count }}{{ $nest->servers_count }}
    -
    -
    -
    -
    - -@endsection - -@section('footer-scripts') - @parent - -@endsection diff --git a/resources/views/admin/nests/new.blade.php b/resources/views/admin/nests/new.blade.php deleted file mode 100644 index 9a1cb0edc8..0000000000 --- a/resources/views/admin/nests/new.blade.php +++ /dev/null @@ -1,47 +0,0 @@ -@extends('layouts.admin') - -@section('title') - New Nest -@endsection - -@section('content-header') -

    New NestConfigure a new nest to deploy to all nodes.

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

    New Nest

    -
    -
    -
    - -
    - -

    This should be a descriptive category name that encompasses all of the eggs within the nest.

    -
    -
    -
    - -
    - -
    -
    -
    - -
    -
    -
    -
    -@endsection diff --git a/resources/views/admin/nests/view.blade.php b/resources/views/admin/nests/view.blade.php deleted file mode 100644 index 9a6730746f..0000000000 --- a/resources/views/admin/nests/view.blade.php +++ /dev/null @@ -1,117 +0,0 @@ -@extends('layouts.admin') - -@section('title') - Nests → {{ $nest->name }} -@endsection - -@section('content-header') -

    {{ $nest->name }}{{ str_limit($nest->description, 50) }}

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

    This should be a descriptive category name that encompasses all of the options within the service.

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

    A unique ID used for identification of this nest internally and through the API.

    -
    -
    -
    - -
    - -

    The author of this service option. Please direct questions and issues to them unless this is an official option authored by support@pterodactyl.io.

    -
    -
    -
    - -
    - -

    A UUID that all servers using this option are assigned for identification purposes.

    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Nest Eggs

    -
    -
    - - - - - - - - - @foreach($nest->eggs as $egg) - - - - - - - - @endforeach -
    IDNameDescriptionServers
    {{ $egg->id }}{{ $egg->name }}{{ $egg->description }}{{ $egg->servers->count() }} - -
    -
    - -
    -
    -
    -@endsection - -@section('footer-scripts') - @parent - -@endsection diff --git a/resources/views/admin/nodes/index.blade.php b/resources/views/admin/nodes/index.blade.php deleted file mode 100644 index 3b3a3fa48f..0000000000 --- a/resources/views/admin/nodes/index.blade.php +++ /dev/null @@ -1,107 +0,0 @@ -@extends('layouts.admin') - -@section('title') - List Nodes -@endsection - -@section('scripts') - @parent - {!! Theme::css('vendor/fontawesome/animation.min.css') !!} -@endsection - -@section('content-header') -

    NodesAll nodes available on the system.

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

    Node List

    -
    -
    -
    - -
    - - -
    -
    -
    -
    -
    -
    - - - - - - - - - - - - - @foreach ($nodes as $node) - - - - - - - - - - - @endforeach - -
    NameLocationMemoryDiskServersSSLPublic
    {!! $node->maintenance_mode ? ' ' : '' !!}{{ $node->name }}{{ $node->location->short }}{{ $node->memory }} MiB{{ $node->disk }} MiB{{ $node->servers_count }}
    -
    - @if($nodes->hasPages()) - - @endif -
    -
    -
    -@endsection - -@section('footer-scripts') - @parent - -@endsection diff --git a/resources/views/admin/nodes/new.blade.php b/resources/views/admin/nodes/new.blade.php deleted file mode 100644 index b10d08a762..0000000000 --- a/resources/views/admin/nodes/new.blade.php +++ /dev/null @@ -1,175 +0,0 @@ -@extends('layouts.admin') - -@section('title') - Nodes → New -@endsection - -@section('content-header') -

    New NodeCreate a new local or remote node for servers to be installed to.

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

    Basic Details

    -
    -
    -
    - - -

    Character limits: a-zA-Z0-9_.- and [Space] (min 1, max 100 characters).

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

    By setting a node to private you will be denying the ability to auto-deploy to this node. -

    -
    - - -

    Please enter domain name (e.g node.example.com) to be used for connecting to the daemon. An IP address may be used only if you are not using SSL for this node.

    -
    -
    - -
    -
    - - -
    -
    - isSecure()) disabled @endif> - -
    -
    - @if(request()->isSecure()) -

    Your Panel is currently configured to use a secure connection. In order for browsers to connect to your node it must use a SSL connection.

    - @else -

    In most cases you should select to use a SSL connection. If using an IP Address or you do not wish to use SSL at all, select a HTTP connection.

    - @endif -
    -
    - -
    -
    - - -
    -
    - - -
    -
    -

    If you are running the daemon behind a proxy such as Cloudflare, select this to have the daemon skip looking for certificates on boot.

    -
    -
    -
    -
    -
    -
    -
    -

    Configuration

    -
    -
    -
    -
    - - -

    Enter the directory where server files should be stored. If you use OVH you should check your partition scheme. You may need to use /home/daemon-data to have enough space.

    -
    -
    - -
    - - MiB -
    -
    -
    - -
    - - % -
    -
    -
    -

    Enter the total amount of memory available for new servers. If you would like to allow overallocation of memory enter the percentage that you want to allow. To disable checking for overallocation enter -1 into the field. Entering 0 will prevent creating new servers if it would put the node over the limit.

    -
    -
    -
    -
    - -
    - - MiB -
    -
    -
    - -
    - - % -
    -
    -
    -

    Enter the total amount of disk space available for new servers. If you would like to allow overallocation of disk space enter the percentage that you want to allow. To disable checking for overallocation enter -1 into the field. Entering 0 will prevent creating new servers if it would put the node over the limit.

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

    The daemon runs its own SFTP management container and does not use the SSHd process on the main physical server. Do not use the same port that you have assigned for your physical server's SSH process. If you will be running the daemon behind CloudFlare® you should set the daemon port to 8443 to allow websocket proxying over SSL.

    -
    -
    -
    - -
    -
    -
    -
    -@endsection - -@section('footer-scripts') - @parent - -@endsection diff --git a/resources/views/admin/nodes/view/allocation.blade.php b/resources/views/admin/nodes/view/allocation.blade.php deleted file mode 100644 index 396428b61b..0000000000 --- a/resources/views/admin/nodes/view/allocation.blade.php +++ /dev/null @@ -1,356 +0,0 @@ -@extends('layouts.admin') - -@section('title') - {{ $node->name }}: Allocations -@endsection - -@section('content-header') -

    {{ $node->name }}Control allocations available for servers on this node.

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

    Existing Allocations

    -
    -
    - - - - - - - - - - @foreach($node->allocations as $allocation) - - - - - - - - - @endforeach -
    - - IP Address IP AliasPortAssigned To - -
    - @if(is_null($allocation->server_id)) - - @else - - @endif - {{ $allocation->ip }} - - - {{ $allocation->port }} - @if(! is_null($allocation->server)) - {{ $allocation->server->name }} - @endif - - @if(is_null($allocation->server_id)) - - @endif -
    -
    - @if($node->allocations->hasPages()) - - @endif -
    -
    -
    -
    -
    -
    -

    Assign New Allocations

    -
    -
    -
    - -
    - -

    Enter an IP address to assign ports to here.

    -
    -
    -
    - -
    - -

    If you would like to assign a default alias to these allocations enter it here.

    -
    -
    -
    - -
    - -

    Enter individual ports or port ranges here separated by commas or spaces.

    -
    -
    -
    - -
    -
    -
    -
    - -@endsection - -@section('footer-scripts') - @parent - -@endsection diff --git a/resources/views/admin/nodes/view/configuration.blade.php b/resources/views/admin/nodes/view/configuration.blade.php deleted file mode 100644 index 1bbf15f337..0000000000 --- a/resources/views/admin/nodes/view/configuration.blade.php +++ /dev/null @@ -1,88 +0,0 @@ -@extends('layouts.admin') - -@section('title') - {{ $node->name }}: Configuration -@endsection - -@section('content-header') -

    {{ $node->name }}Your daemon configuration file.

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

    Configuration File

    -
    -
    -
    {{ $node->getYamlConfiguration() }}
    -
    - -
    -
    -
    -
    -
    -

    Auto-Deploy

    -
    -
    -

    - Use the button below to generate a custom deployment command that can be used to configure - wings on the target server with a single command. -

    -
    - -
    -
    -
    -@endsection - -@section('footer-scripts') - @parent - -@endsection diff --git a/resources/views/admin/nodes/view/index.blade.php b/resources/views/admin/nodes/view/index.blade.php deleted file mode 100644 index 2d0bb32874..0000000000 --- a/resources/views/admin/nodes/view/index.blade.php +++ /dev/null @@ -1,164 +0,0 @@ -@extends('layouts.admin') - -@section('title') - {{ $node->name }} -@endsection - -@section('content-header') -

    {{ $node->name }}A quick overview of your node.

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

    Information

    -
    -
    - - - - - - - - - - - - - -
    Daemon Version (Latest: {{ $version->getDaemon() }})
    System Information
    Total CPU Threads
    -
    -
    -
    - @if ($node->description) -
    -
    -
    - Description -
    -
    -
    {{ $node->description }}
    -
    -
    -
    - @endif -
    -
    -
    -

    Delete Node

    -
    -
    -

    Deleting a node is a irreversible action and will immediately remove this node from the panel. There must be no servers associated with this node in order to continue.

    -
    - -
    -
    -
    -
    -
    -
    -
    -

    At-a-Glance

    -
    -
    -
    - @if($node->maintenance_mode) -
    -
    - -
    - This node is under - Maintenance -
    -
    -
    - @endif -
    -
    - -
    - Disk Space Allocated - {{ $stats['disk']['value'] }} / {{ $stats['disk']['max'] }} MiB -
    -
    -
    -
    -
    -
    -
    -
    - -
    - Memory Allocated - {{ $stats['memory']['value'] }} / {{ $stats['memory']['max'] }} MiB -
    -
    -
    -
    -
    -
    -
    -
    - -
    - Total Servers - {{ $node->servers_count }} -
    -
    -
    -
    -
    -
    -
    -
    -@endsection - -@section('footer-scripts') - @parent - -@endsection diff --git a/resources/views/admin/nodes/view/servers.blade.php b/resources/views/admin/nodes/view/servers.blade.php deleted file mode 100644 index b8b9ee514b..0000000000 --- a/resources/views/admin/nodes/view/servers.blade.php +++ /dev/null @@ -1,63 +0,0 @@ -@extends('layouts.admin') - -@section('title') - {{ $node->name }}: Servers -@endsection - -@section('content-header') -

    {{ $node->name }}All servers currently assigned to this node.

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

    Process Manager

    -
    -
    - - - - - - - - @foreach($servers as $server) - - - - - - - @endforeach -
    IDServer NameOwnerService
    {{ $server->uuidShort }}{{ $server->name }}{{ $server->user->username }}{{ $server->nest->name }} ({{ $server->egg->name }})
    - @if($servers->hasPages()) - - @endif -
    -
    -
    -
    -@endsection diff --git a/resources/views/admin/nodes/view/settings.blade.php b/resources/views/admin/nodes/view/settings.blade.php deleted file mode 100644 index bfc9d8caf7..0000000000 --- a/resources/views/admin/nodes/view/settings.blade.php +++ /dev/null @@ -1,240 +0,0 @@ -@extends('layouts.admin') - -@section('title') - {{ $node->name }}: Settings -@endsection - -@section('content-header') -

    {{ $node->name }}Configure your node settings.

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

    Settings

    -
    -
    -
    - -
    - -

    Character limits: a-zA-Z0-9_.- and [Space] (min 1, max 100 characters).

    -
    -
    -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    - -
    - public)) ? 'checked' : '' }} id="public_1" checked>
    - public)) ? '' : 'checked' }} id="public_0"> -
    -
    -
    - -
    - -
    -

    Please enter domain name (e.g node.example.com) to be used for connecting to the daemon. An IP address may only be used if you are not using SSL for this node. - Why? -

    -
    -
    - -
    -
    - scheme) === 'https') ? 'checked' : '' }}> - -
    -
    - scheme) !== 'https') ? 'checked' : '' }}> - -
    -
    -

    In most cases you should select to use a SSL connection. If using an IP Address or you do not wish to use SSL at all, select a HTTP connection.

    -
    -
    - -
    -
    - behind_proxy) == false) ? 'checked' : '' }}> - -
    -
    - behind_proxy) == true) ? 'checked' : '' }}> - -
    -
    -

    If you are running the daemon behind a proxy such as Cloudflare, select this to have the daemon skip looking for certificates on boot.

    -
    -
    - -
    -
    - maintenance_mode) == false) ? 'checked' : '' }}> - -
    -
    - maintenance_mode) == true) ? 'checked' : '' }}> - -
    -
    -

    If the node is marked as 'Under Maintenance' users won't be able to access servers that are on this node.

    -
    -
    -
    -
    -
    -
    -
    -

    Allocation Limits

    -
    -
    -
    -
    -
    - -
    - - MiB -
    -
    -
    - -
    - - % -
    -
    -
    -

    Enter the total amount of memory available on this node for allocation to servers. You may also provide a percentage that can allow allocation of more than the defined memory.

    -
    -
    -
    -
    - -
    - - MiB -
    -
    -
    - -
    - - % -
    -
    -
    -

    Enter the total amount of disk space available on this node for server allocation. You may also provide a percentage that will determine the amount of disk space over the set limit to allow.

    -
    -
    -
    -
    -
    -
    -
    -

    General Configuration

    -
    -
    -
    - -
    - - MiB -
    -

    Enter the maximum size of files that can be uploaded through the web-based file manager.

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

    The daemon runs its own SFTP management container and does not use the SSHd process on the main physical server. Do not use the same port that you have assigned for your physical server's SSH process.

    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Save Settings

    -
    -
    -
    -
    - -
    -

    Resetting the daemon master key will void any request coming from the old key. This key is used for all sensitive operations on the daemon including server creation and deletion. We suggest changing this key regularly for security.

    -
    -
    - -
    -
    -
    -
    -@endsection - -@section('footer-scripts') - @parent - -@endsection diff --git a/resources/views/admin/servers/index.blade.php b/resources/views/admin/servers/index.blade.php deleted file mode 100644 index 4d5ebcdcdf..0000000000 --- a/resources/views/admin/servers/index.blade.php +++ /dev/null @@ -1,89 +0,0 @@ -@extends('layouts.admin') - -@section('title') - List Servers -@endsection - -@section('content-header') -

    ServersAll servers available on the system.

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

    Server List

    -
    -
    -
    - -
    - - -
    -
    -
    -
    -
    -
    - - - - - - - - - - - - @foreach ($servers as $server) - - - - - - - - - - @endforeach - -
    Server NameUUIDOwnerNodeConnection
    {{ $server->name }}{{ $server->uuid }}{{ $server->user->username }}{{ $server->node->name }} - {{ $server->allocation->alias }}:{{ $server->allocation->port }} - - @if($server->isSuspended()) - Suspended - @elseif(! $server->isInstalled()) - Installing - @else - Active - @endif - - -
    -
    - @if($servers->hasPages()) - - @endif -
    -
    -
    -@endsection - -@section('footer-scripts') - @parent - -@endsection diff --git a/resources/views/admin/servers/new.blade.php b/resources/views/admin/servers/new.blade.php deleted file mode 100644 index 4198634f1e..0000000000 --- a/resources/views/admin/servers/new.blade.php +++ /dev/null @@ -1,395 +0,0 @@ -@extends('layouts.admin') - -@section('title') - New Server -@endsection - -@section('content-header') -

    Create ServerAdd a new server to the panel.

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

    Core Details

    -
    - -
    -
    -
    - - -

    Character limits: a-z A-Z 0-9 _ - . and [Space].

    -
    - -
    - - -

    Email address of the Server Owner.

    -
    -
    - -
    -
    - - -

    A brief description of this server.

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

    Allocation Management

    -
    - -
    -
    - - - -

    The node which this server will be deployed to.

    -
    - -
    - - -

    The main allocation that will be assigned to this server.

    -
    - -
    - - -

    Additional allocations to assign to this server on creation.

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

    Application Feature Limits

    -
    - -
    -
    - -
    - -
    -

    The total number of databases a user is allowed to create for this server.

    -
    -
    - -
    - -
    -

    The total number of allocations a user is allowed to create for this server.

    -
    -
    - -
    - -
    -

    The total number of backups that can be created for this server.

    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Resource Management

    -
    - -
    -
    - - -
    - - % -
    - -

    If you do not want to limit CPU usage, set the value to 0. To determine a value, take the number of threads and multiply it by 100. For example, on a quad core system without hyperthreading (4 * 100 = 400) there is 400% available. To limit a server to using half of a single thread, you would set the value to 50. To allow a server to use up to two threads, set the value to 200.

    -

    - -
    - - -
    - -
    - -

    Advanced: Enter the specific CPU threads that this process can run on, or leave blank to allow all threads. This can be a single number, or a comma separated list. Example: 0, 0-1,3, or 0,1,3,4.

    -
    -
    - -
    -
    - - -
    - - MiB -
    - -

    The maximum amount of memory allowed for this container. Setting this to 0 will allow unlimited memory in a container.

    -
    - -
    - - -
    - - MiB -
    - -

    Setting this to 0 will disable swap space on this server. Setting to -1 will allow unlimited swap.

    -
    -
    - -
    -
    - - -
    - - MiB -
    - -

    This server will not be allowed to boot if it is using more than this amount of space. If a server goes over this limit while running it will be safely stopped and locked until enough space is available. Set to 0 to allow unlimited disk usage.

    -
    - -
    - - -
    - -
    - -

    Advanced: The IO performance of this server relative to other running containers on the system. Value should be between 10 and 1000. Please see this documentation for more information about it.

    -
    -
    -
    - - -
    - -

    Terminates the server if it breaches the memory limits. Enabling OOM killer may cause server processes to exit unexpectedly.

    -
    -
    -
    -
    -
    - -
    -
    -
    -
    -

    Nest Configuration

    -
    - -
    -
    - - - - -

    Select the Nest that this server will be grouped under.

    -
    - -
    - - -

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

    -
    -
    -
    - - -
    - -

    If the selected Egg has an install script attached to it, the script will run during the install. If you would like to skip this step, check this box.

    -
    -
    -
    -
    - -
    -
    -
    -

    Docker Configuration

    -
    - -
    -
    - - - -

    This is the default Docker image that will be used to run this server. Select an image from the dropdown above, or enter a custom image in the text field above.

    -
    -
    -
    -
    -
    - -
    -
    -
    -
    -

    Startup Configuration

    -
    - -
    -
    - - -

    The following data substitutes are available for the startup command: @{{SERVER_MEMORY}}, @{{SERVER_IP}}, and @{{SERVER_PORT}}. They will be replaced with the allocated memory, server IP, and server port respectively.

    -
    -
    - -
    -

    Service Variables

    -
    - -
    - - -
    -
    -
    -
    -@endsection - -@section('footer-scripts') - @parent - {!! Theme::js('vendor/lodash/lodash.js') !!} - - - - {!! Theme::js('js/admin/new-server.js?v=20220530') !!} - - -@endsection diff --git a/resources/views/admin/servers/partials/navigation.blade.php b/resources/views/admin/servers/partials/navigation.blade.php deleted file mode 100644 index 964eac8e3a..0000000000 --- a/resources/views/admin/servers/partials/navigation.blade.php +++ /dev/null @@ -1,40 +0,0 @@ -@php - /** @var \Pterodactyl\Models\Server $server */ - $router = app('router'); -@endphp -
    -
    - -
    -
    diff --git a/resources/views/admin/servers/view/build.blade.php b/resources/views/admin/servers/view/build.blade.php deleted file mode 100644 index 655ea36af3..0000000000 --- a/resources/views/admin/servers/view/build.blade.php +++ /dev/null @@ -1,187 +0,0 @@ -@extends('layouts.admin') - -@section('title') - Server — {{ $server->name }}: Build Details -@endsection - -@section('content-header') -

    {{ $server->name }}Control allocations and system resources for this server.

    - -@endsection - -@section('content') -@include('admin.servers.partials.navigation') -
    -
    -
    -
    -
    -

    Resource Management

    -
    -
    -
    - -
    - - % -
    -

    Each virtual core (thread) on the system is considered to be 100%. Setting this value to 0 will allow a server to use CPU time without restrictions.

    -
    -
    - -
    - -
    -

    Advanced: Enter the specific CPU cores that this process can run on, or leave blank to allow all cores. This can be a single number, or a comma seperated list. Example: 0, 0-1,3, or 0,1,3,4.

    -
    -
    - -
    - - MiB -
    -

    The maximum amount of memory allowed for this container. Setting this to 0 will allow unlimited memory in a container.

    -
    -
    - -
    - - MiB -
    -

    Setting this to 0 will disable swap space on this server. Setting to -1 will allow unlimited swap.

    -
    -
    - -
    - - MiB -
    -

    This server will not be allowed to boot if it is using more than this amount of space. If a server goes over this limit while running it will be safely stopped and locked until enough space is available. Set to 0 to allow unlimited disk usage.

    -
    -
    - -
    - -
    -

    Advanced: The IO performance of this server relative to other running containers on the system. Value should be between 10 and 1000.

    -
    -
    - -
    -
    - oom_disabled)checked @endif> - -
    -
    - oom_disabled)checked @endif> - -
    -

    - Enabling OOM killer may cause server processes to exit unexpectedly. -

    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Application Feature Limits

    -
    -
    -
    -
    - -
    - -
    -

    The total number of databases a user is allowed to create for this server.

    -
    -
    - -
    - -
    -

    The total number of allocations a user is allowed to create for this server.

    -
    -
    - -
    - -
    -

    The total number of backups that can be created for this server.

    -
    -
    -
    -
    -
    -
    -
    -
    -

    Allocation Management

    -
    -
    -
    - - -

    The default connection address that will be used for this game server.

    -
    -
    - -
    - -
    -

    Please note that due to software limitations you cannot assign identical ports on different IPs to the same server.

    -
    -
    - -
    - -
    -

    Simply select which ports you would like to remove from the list above. If you want to assign a port on a different IP that is already in use you can select it from the left and delete it here.

    -
    -
    - -
    -
    -
    -
    -
    -
    -@endsection - -@section('footer-scripts') - @parent - -@endsection diff --git a/resources/views/admin/servers/view/database.blade.php b/resources/views/admin/servers/view/database.blade.php deleted file mode 100644 index 385ce0f3f3..0000000000 --- a/resources/views/admin/servers/view/database.blade.php +++ /dev/null @@ -1,169 +0,0 @@ -@extends('layouts.admin') - -@section('title') - Server — {{ $server->name }}: Databases -@endsection - -@section('content-header') -

    {{ $server->name }}Manage server databases.

    - -@endsection - -@section('content') -@include('admin.servers.partials.navigation') -
    -
    -
    - Database passwords can be viewed when visiting this server on the front-end. -
    -
    -
    -

    Active Databases

    -
    -
    - - - - - - - - - - @foreach($server->databases as $database) - - - - - - @if($database->max_connections != null) - - @else - - @endif - - - @endforeach -
    DatabaseUsernameConnections FromHostMax Connections
    {{ $database->database }}{{ $database->username }}{{ $database->remote }}{{ $database->host->host }}:{{ $database->host->port }}{{ $database->max_connections }}Unlimited - - -
    -
    -
    -
    -
    -
    -
    -

    Create New Database

    -
    -
    -
    -
    - - -

    Select the host database server that this database should be created on.

    -
    -
    - -
    - s{{ $server->id }}_ - -
    -
    -
    - - -

    This should reflect the IP address that connections are allowed from. Uses standard MySQL notation. If unsure leave as %.

    -
    -
    - - -

    This should reflect the max number of concurrent connections from this user to the database. Leave empty for unlimited.

    -
    -
    - -
    -
    -
    -
    -@endsection - -@section('footer-scripts') - @parent - -@endsection diff --git a/resources/views/admin/servers/view/delete.blade.php b/resources/views/admin/servers/view/delete.blade.php deleted file mode 100644 index c237096321..0000000000 --- a/resources/views/admin/servers/view/delete.blade.php +++ /dev/null @@ -1,91 +0,0 @@ -@extends('layouts.admin') - -@section('title') - Server — {{ $server->name }}: Delete -@endsection - -@section('content-header') -

    {{ $server->name }}Delete this server from the panel.

    - -@endsection - -@section('content') -@include('admin.servers.partials.navigation') -
    -
    -
    -
    -

    Safely Delete Server

    -
    -
    -

    This action will attempt to delete the server from both the panel and daemon. If either one reports an error the action will be cancelled.

    -

    Deleting a server is an irreversible action. All server data (including files and users) will be removed from the system.

    -
    - -
    -
    -
    -
    -
    -

    Force Delete Server

    -
    -
    -

    This action will attempt to delete the server from both the panel and daemon. If the daemon does not respond, or reports an error the deletion will continue.

    -

    Deleting a server is an irreversible action. All server data (including files and users) will be removed from the system. This method may leave dangling files on your daemon if it reports an error.

    -
    - -
    -
    -
    -@endsection - -@section('footer-scripts') - @parent - -@endsection diff --git a/resources/views/admin/servers/view/details.blade.php b/resources/views/admin/servers/view/details.blade.php deleted file mode 100644 index 9b4464b2cc..0000000000 --- a/resources/views/admin/servers/view/details.blade.php +++ /dev/null @@ -1,121 +0,0 @@ -@extends('layouts.admin') - -@section('title') - Server — {{ $server->name }}: Details -@endsection - -@section('content-header') -

    {{ $server->name }}Edit details for this server including owner and container.

    - -@endsection - -@section('content') -@include('admin.servers.partials.navigation') -
    -
    -
    -
    -

    Base Information

    -
    -
    -
    -
    - - -

    Character limits: a-zA-Z0-9_- and [Space].

    -
    -
    - - -

    Leave empty to not assign an external identifier for this server. The external ID should be unique to this server and not be in use by any other servers.

    -
    -
    - - -

    You can change the owner of this server by changing this field to an email matching another use on this system. If you do this a new daemon security token will be generated automatically.

    -
    -
    - - -

    A brief description of this server.

    -
    -
    - -
    -
    -
    -
    -@endsection - -@section('footer-scripts') - @parent - -@endsection diff --git a/resources/views/admin/servers/view/index.blade.php b/resources/views/admin/servers/view/index.blade.php deleted file mode 100644 index f94c6d42fc..0000000000 --- a/resources/views/admin/servers/view/index.blade.php +++ /dev/null @@ -1,178 +0,0 @@ -@extends('layouts.admin') - -@section('title') - Server — {{ $server->name }} -@endsection - -@section('content-header') -

    {{ $server->name }}{{ str_limit($server->description) }}

    - -@endsection - -@section('content') -@include('admin.servers.partials.navigation') -
    -
    -
    -
    -
    -
    -

    Information

    -
    -
    - - - - - - - - @if(is_null($server->external_id)) - - @else - - @endif - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Internal Identifier{{ $server->id }}
    External IdentifierNot Set{{ $server->external_id }}
    UUID / Docker Container ID{{ $server->uuid }}
    Current Egg - {{ $server->nest->name }} :: - {{ $server->egg->name }} -
    Server Name{{ $server->name }}
    CPU Limit - @if($server->cpu === 0) - Unlimited - @else - {{ $server->cpu }}% - @endif -
    CPU Pinning - @if($server->threads != null) - {{ $server->threads }} - @else - Not Set - @endif -
    Memory - @if($server->memory === 0) - Unlimited - @else - {{ $server->memory }}MiB - @endif - / - @if($server->swap === 0) - Not Set - @elseif($server->swap === -1) - Unlimited - @else - {{ $server->swap }}MiB - @endif -
    Disk Space - @if($server->disk === 0) - Unlimited - @else - {{ $server->disk }}MiB - @endif -
    Block IO Weight{{ $server->io }}
    Default Connection{{ $server->allocation->ip }}:{{ $server->allocation->port }}
    Connection Alias - @if($server->allocation->alias !== $server->allocation->ip) - {{ $server->allocation->alias }}:{{ $server->allocation->port }} - @else - No Alias Assigned - @endif -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - @if($server->isSuspended()) -
    -
    -
    -

    Suspended

    -
    -
    -
    - @endif - @if(!$server->isInstalled()) -
    -
    -
    -

    {{ (! $server->isInstalled()) ? 'Installing' : 'Install Failed' }}

    -
    -
    -
    - @endif -
    -
    -
    -

    {{ str_limit($server->user->username, 16) }}

    -

    Server Owner

    -
    -
    - - More info - -
    -
    -
    -
    -
    -

    {{ str_limit($server->node->name, 16) }}

    -

    Server Node

    -
    -
    - - More info - -
    -
    -
    -
    -
    -
    -
    -@endsection diff --git a/resources/views/admin/servers/view/manage.blade.php b/resources/views/admin/servers/view/manage.blade.php deleted file mode 100644 index e6177a43b2..0000000000 --- a/resources/views/admin/servers/view/manage.blade.php +++ /dev/null @@ -1,202 +0,0 @@ -@extends('layouts.admin') - -@section('title') - Server — {{ $server->name }}: Manage -@endsection - -@section('content-header') -

    {{ $server->name }}Additional actions to control this server.

    - -@endsection - -@section('content') - @include('admin.servers.partials.navigation') -
    -
    -
    -
    -

    Reinstall Server

    -
    -
    -

    This will reinstall the server with the assigned service scripts. Danger! This could overwrite server data.

    -
    - -
    -
    -
    -
    -
    -

    Install Status

    -
    -
    -

    If you need to change the install status from uninstalled to installed, or vice versa, you may do so with the button below.

    -
    - -
    -
    - - @if(! $server->isSuspended()) -
    -
    -
    -

    Suspend Server

    -
    -
    -

    This will suspend the server, stop any running processes, and immediately block the user from being able to access their files or otherwise manage the server through the panel or API.

    -
    - -
    -
    - @else -
    -
    -
    -

    Unsuspend Server

    -
    -
    -

    This will unsuspend the server and restore normal user access.

    -
    - -
    -
    - @endif - - @if(is_null($server->transfer)) -
    -
    -
    -

    Transfer Server

    -
    -
    -

    - Transfer this server to another node connected to this panel. - Warning! This feature has not been fully tested and may have bugs. -

    -
    - - -
    -
    - @else -
    -
    -
    -

    Transfer Server

    -
    -
    -

    - This server is currently being transferred to another node. - Transfer was initiated at {{ $server->transfer->created_at }} -

    -
    - - -
    -
    - @endif -
    - - -@endsection - -@section('footer-scripts') - @parent - {!! Theme::js('vendor/lodash/lodash.js') !!} - - @if($canTransfer) - {!! Theme::js('js/admin/server/transfer.js') !!} - @endif -@endsection diff --git a/resources/views/admin/servers/view/mounts.blade.php b/resources/views/admin/servers/view/mounts.blade.php deleted file mode 100644 index 36ca98ddc0..0000000000 --- a/resources/views/admin/servers/view/mounts.blade.php +++ /dev/null @@ -1,78 +0,0 @@ -@extends('layouts.admin') - -@section('title') - Server — {{ $server->name }}: Mounts -@endsection - -@section('content-header') -

    {{ $server->name }}Manage server mounts.

    - -@endsection - -@section('content') - @include('admin.servers.partials.navigation') - -
    -
    -
    -
    -

    Available Mounts

    -
    - -
    - - - - - - - - - - - @foreach ($mounts as $mount) - - - - - - - @if (! in_array($mount->id, $server->mounts->pluck('id')->toArray())) - - - - @else - - - - @endif - - @endforeach -
    IDNameSourceTargetStatus
    {{ $mount->id }}{{ $mount->name }}{{ $mount->source }}{{ $mount->target }} - Unmounted - -
    - {!! csrf_field() !!} - - -
    -
    - Mounted - -
    - @method('DELETE') - {!! csrf_field() !!} - - -
    -
    -
    -
    -
    -
    -@endsection diff --git a/resources/views/admin/servers/view/startup.blade.php b/resources/views/admin/servers/view/startup.blade.php deleted file mode 100644 index 05330298e8..0000000000 --- a/resources/views/admin/servers/view/startup.blade.php +++ /dev/null @@ -1,186 +0,0 @@ -@extends('layouts.admin') - -@section('title') - Server — {{ $server->name }}: Startup -@endsection - -@section('content-header') -

    {{ $server->name }}Control startup command as well as variables.

    - -@endsection - -@section('content') -@include('admin.servers.partials.navigation') -
    -
    -
    -
    -
    -

    Startup Command Modification

    -
    -
    - - -

    Edit your server's startup command here. The following variables are available by default: @{{SERVER_MEMORY}}, @{{SERVER_IP}}, and @{{SERVER_PORT}}.

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

    Service Configuration

    -
    -
    -
    -

    - Changing any of the below values will result in the server processing a re-install command. The server will be stopped and will then proceed. - If you would like the service scripts to not run, ensure the box is checked at the bottom. -

    -

    - This is a destructive operation in many cases. This server will be stopped immediately in order for this action to proceed. -

    -
    -
    - - -

    Select the Nest that this server will be grouped into.

    -
    -
    - - -

    Select the Egg that will provide processing data for this server.

    -
    -
    -
    - skip_scripts) checked @endif /> - -
    -

    If the selected Egg has an install script attached to it, the script will run during install. If you would like to skip this step, check this box.

    -
    -
    -
    -
    -
    -

    Docker Image Configuration

    -
    -
    -
    - - - -

    This is the Docker image that will be used to run this server. Select an image from the dropdown or enter a custom image in the text field above.

    -
    -
    -
    -
    -
    -
    -
    -
    -
    -@endsection - -@section('footer-scripts') - @parent - {!! Theme::js('vendor/lodash/lodash.js') !!} - -@endsection diff --git a/resources/views/admin/settings/advanced.blade.php b/resources/views/admin/settings/advanced.blade.php deleted file mode 100644 index 0f2056ca48..0000000000 --- a/resources/views/admin/settings/advanced.blade.php +++ /dev/null @@ -1,127 +0,0 @@ -@extends('layouts.admin') -@include('partials/admin.settings.nav', ['activeTab' => 'advanced']) - -@section('title') - Advanced Settings -@endsection - -@section('content-header') -

    Advanced SettingsConfigure advanced settings for Pterodactyl.

    - -@endsection - -@section('content') - @yield('settings::nav') -
    -
    -
    -
    -
    -

    reCAPTCHA

    -
    -
    -
    -
    - -
    - -

    If enabled, login forms and password reset forms will do a silent captcha check and display a visible captcha if needed.

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

    Used for communication between your site and Google. Be sure to keep it a secret.

    -
    -
    -
    - @if($showRecaptchaWarning) -
    -
    -
    - You are currently using reCAPTCHA keys that were shipped with this Panel. For improved security it is recommended to generate new invisible reCAPTCHA keys that tied specifically to your website. -
    -
    -
    - @endif -
    -
    -
    -
    -

    HTTP Connections

    -
    -
    -
    -
    - -
    - -

    The amount of time in seconds to wait for a connection to be opened before throwing an error.

    -
    -
    -
    - -
    - -

    The amount of time in seconds to wait for a request to be completed before throwing an error.

    -
    -
    -
    -
    -
    -
    -
    -

    Automatic Allocation Creation

    -
    -
    -
    -
    - -
    - -

    If enabled users will have the option to automatically create new allocations for their server via the frontend.

    -
    -
    -
    - -
    - -

    The starting port in the range that can be automatically allocated.

    -
    -
    -
    - -
    - -

    The ending port in the range that can be automatically allocated.

    -
    -
    -
    -
    -
    -
    - -
    -
    -
    -
    -@endsection diff --git a/resources/views/admin/settings/index.blade.php b/resources/views/admin/settings/index.blade.php deleted file mode 100644 index 489646dc97..0000000000 --- a/resources/views/admin/settings/index.blade.php +++ /dev/null @@ -1,75 +0,0 @@ -@extends('layouts.admin') -@include('partials/admin.settings.nav', ['activeTab' => 'basic']) - -@section('title') - Settings -@endsection - -@section('content-header') -

    Panel SettingsConfigure Pterodactyl to your liking.

    - -@endsection - -@section('content') - @yield('settings::nav') -
    -
    -
    -
    -

    Panel Settings

    -
    -
    -
    -
    -
    - -
    - -

    This is the name that is used throughout the panel and in emails sent to clients.

    -
    -
    -
    - -
    -
    - @php - $level = old('pterodactyl:auth:2fa_required', config('pterodactyl.auth.2fa_required')); - @endphp - - - -
    -

    If enabled, any account falling into the selected grouping will be required to have 2-Factor authentication enabled to use the Panel.

    -
    -
    -
    - -
    - -

    The default language to use when rendering UI components.

    -
    -
    -
    -
    - -
    -
    -
    -
    -@endsection diff --git a/resources/views/admin/settings/mail.blade.php b/resources/views/admin/settings/mail.blade.php deleted file mode 100644 index 9e99acd308..0000000000 --- a/resources/views/admin/settings/mail.blade.php +++ /dev/null @@ -1,202 +0,0 @@ -@extends('layouts.admin') -@include('partials/admin.settings.nav', ['activeTab' => 'mail']) - -@section('title') - Mail Settings -@endsection - -@section('content-header') -

    Mail SettingsConfigure how Pterodactyl should handle sending emails.

    - -@endsection - -@section('content') - @yield('settings::nav') -
    -
    -
    -
    -

    Email Settings

    -
    - @if($disabled) -
    -
    -
    -
    - This interface is limited to instances using SMTP as the mail driver. Please either use php artisan p:environment:mail command to update your email settings, or set MAIL_DRIVER=smtp in your environment file. -
    -
    -
    -
    - @else -
    -
    -
    -
    - -
    - -

    Enter the SMTP server address that mail should be sent through.

    -
    -
    -
    - -
    - -

    Enter the SMTP server port that mail should be sent through.

    -
    -
    -
    - -
    - @php - $encryption = old('mail:mailers:smtp:encryption', config('mail.mailers.smtp.encryption')); - @endphp - -

    Select the type of encryption to use when sending mail.

    -
    -
    -
    - -
    - -

    The username to use when connecting to the SMTP server.

    -
    -
    -
    - -
    - -

    The password to use in conjunction with the SMTP username. Leave blank to continue using the existing password. To set the password to an empty value enter !e into the field.

    -
    -
    -
    -
    -
    -
    - -
    - -

    Enter an email address that all outgoing emails will originate from.

    -
    -
    -
    - -
    - -

    The name that emails should appear to come from.

    -
    -
    -
    -
    - -
    - @endif -
    -
    -
    -@endsection - -@section('footer-scripts') - @parent - - -@endsection diff --git a/resources/views/admin/users/index.blade.php b/resources/views/admin/users/index.blade.php deleted file mode 100644 index 0c8e906c3d..0000000000 --- a/resources/views/admin/users/index.blade.php +++ /dev/null @@ -1,79 +0,0 @@ -@extends('layouts.admin') - -@section('title') - List Users -@endsection - -@section('content-header') -

    UsersAll registered users on the system.

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

    User List

    -
    -
    -
    - -
    - - -
    -
    -
    -
    -
    -
    - - - - - - - - - - - - - - - @foreach ($users as $user) - - - - - - - - - - - @endforeach - -
    IDEmailClient NameUsername2FAServers OwnedCan Access
    {{ $user->id }}{{ $user->email }} @if($user->root_admin)@endif{{ $user->name_last }}, {{ $user->name_first }}{{ $user->username }} - @if($user->use_totp) - - @else - - @endif - - {{ $user->servers_count }} - {{ $user->subuser_of_count }}
    -
    - @if($users->hasPages()) - - @endif -
    -
    -
    -@endsection diff --git a/resources/views/admin/users/new.blade.php b/resources/views/admin/users/new.blade.php deleted file mode 100644 index 2ff50164d7..0000000000 --- a/resources/views/admin/users/new.blade.php +++ /dev/null @@ -1,128 +0,0 @@ -@extends('layouts.admin') - -@section('title') - Create User -@endsection - -@section('content-header') -

    Create UserAdd a new user to the system.

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

    Identity

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

    The default language to use when rendering the Panel for this user.

    -
    -
    -
    - -
    -
    -
    -
    -
    -

    Permissions

    -
    -
    -
    - -
    - -

    Setting this to 'Yes' gives a user full administrative access.

    -
    -
    -
    -
    -
    -
    -
    -
    -

    Password

    -
    -
    -
    -

    Providing a user password is optional. New user emails prompt users to create a password the first time they login. If a password is provided here you will need to find a different method of providing it to the user.

    -
    - -
    - -
    - -
    -
    -
    -
    -
    -
    -
    -@endsection - -@section('footer-scripts') - @parent - -@endsection diff --git a/resources/views/admin/users/view.blade.php b/resources/views/admin/users/view.blade.php deleted file mode 100644 index f042d892d4..0000000000 --- a/resources/views/admin/users/view.blade.php +++ /dev/null @@ -1,123 +0,0 @@ -@extends('layouts.admin') - -@section('title') - Manager User: {{ $user->username }} -@endsection - -@section('content-header') -

    {{ $user->name_first }} {{ $user->name_last}}{{ $user->username }}

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

    Identity

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

    The default language to use when rendering the Panel for this user.

    -
    -
    -
    - -
    -
    -
    -
    -
    -

    Password

    -
    -
    - -
    - -
    - -

    Leave blank to keep this user's password the same. User will not receive any notification if password is changed.

    -
    -
    -
    -
    -
    -
    -
    -
    -

    Permissions

    -
    -
    -
    - -
    - -

    Setting this to 'Yes' gives a user full administrative access.

    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Delete User

    -
    -
    -

    There must be no servers associated with this account in order for it to be deleted.

    -
    - -
    -
    -
    -@endsection diff --git a/resources/views/layouts/admin.blade.php b/resources/views/layouts/admin.blade.php deleted file mode 100644 index 1543ddf72e..0000000000 --- a/resources/views/layouts/admin.blade.php +++ /dev/null @@ -1,214 +0,0 @@ - - - - - - {{ config('app.name', 'Pterodactyl') }} - @yield('title') - - - - - - - - - - - - - @include('layouts.scripts') - - @section('scripts') - {!! Theme::css('vendor/select2/select2.min.css?t={cache-version}') !!} - {!! Theme::css('vendor/bootstrap/bootstrap.min.css?t={cache-version}') !!} - {!! Theme::css('vendor/adminlte/admin.min.css?t={cache-version}') !!} - {!! Theme::css('vendor/adminlte/colors/skin-blue.min.css?t={cache-version}') !!} - {!! Theme::css('vendor/sweetalert/sweetalert.min.css?t={cache-version}') !!} - {!! Theme::css('vendor/animate/animate.min.css?t={cache-version}') !!} - {!! Theme::css('css/pterodactyl.css?t={cache-version}') !!} - - - - - @show - - -
    -
    - - -
    - -
    -
    - @yield('content-header') -
    -
    -
    -
    - @if (count($errors) > 0) -
    - There was an error validating the data provided.

    -
      - @foreach ($errors->all() as $error) -
    • {{ $error }}
    • - @endforeach -
    -
    - @endif - @foreach (Alert::getMessages() as $type => $messages) - @foreach ($messages as $message) - - @endforeach - @endforeach -
    -
    - @yield('content') -
    -
    -
    -
    - {{ $appVersion }}
    - {{ round(microtime(true) - LARAVEL_START, 3) }}s -
    - Copyright © 2015 - {{ date('Y') }} Pterodactyl Software. -
    -
    - @section('footer-scripts') - - - - {!! Theme::js('vendor/jquery/jquery.min.js?t={cache-version}') !!} - {!! Theme::js('vendor/sweetalert/sweetalert.min.js?t={cache-version}') !!} - {!! Theme::js('vendor/bootstrap/bootstrap.min.js?t={cache-version}') !!} - {!! Theme::js('vendor/slimscroll/jquery.slimscroll.min.js?t={cache-version}') !!} - {!! Theme::js('vendor/adminlte/app.min.js?t={cache-version}') !!} - {!! Theme::js('vendor/bootstrap-notify/bootstrap-notify.min.js?t={cache-version}') !!} - {!! Theme::js('vendor/select2/select2.full.min.js?t={cache-version}') !!} - {!! Theme::js('js/admin/functions.js?t={cache-version}') !!} - - - @if(Auth::user()->root_admin) - - @endif - - - @show - - diff --git a/resources/views/partials/admin/settings/nav.blade.php b/resources/views/partials/admin/settings/nav.blade.php deleted file mode 100644 index 9f1ace7f31..0000000000 --- a/resources/views/partials/admin/settings/nav.blade.php +++ /dev/null @@ -1,16 +0,0 @@ -@include('partials/admin.settings.notice') - -@section('settings::nav') - @yield('settings::notice') -
    -
    - -
    -
    -@endsection diff --git a/resources/views/partials/admin/settings/notice.blade.php b/resources/views/partials/admin/settings/notice.blade.php deleted file mode 100644 index 2dc0bc112f..0000000000 --- a/resources/views/partials/admin/settings/notice.blade.php +++ /dev/null @@ -1,11 +0,0 @@ -@section('settings::notice') - @if(config('pterodactyl.load_environment_only', false)) -
    -
    -
    - Your Panel is currently configured to read settings from the environment only. You will need to set APP_ENVIRONMENT_ONLY=false in your environment file in order to load settings dynamically. -
    -
    -
    - @endif -@endsection diff --git a/resources/views/partials/schedules/task-template.blade.php b/resources/views/partials/schedules/task-template.blade.php deleted file mode 100644 index efcbbc47e9..0000000000 --- a/resources/views/partials/schedules/task-template.blade.php +++ /dev/null @@ -1,42 +0,0 @@ -@section('tasks::chain-template') - -@show diff --git a/routes/admin.php b/routes/admin.php index 4c6732e762..8fd9d5ba44 100644 --- a/routes/admin.php +++ b/routes/admin.php @@ -2,227 +2,6 @@ use Illuminate\Support\Facades\Route; use Pterodactyl\Http\Controllers\Admin; -use Pterodactyl\Http\Middleware\Admin\Servers\ServerInstalled; -Route::get('/', [Admin\BaseController::class, 'index'])->name('admin.index'); - -/* -|-------------------------------------------------------------------------- -| Location Controller Routes -|-------------------------------------------------------------------------- -| -| Endpoint: /admin/api -| -*/ -Route::group(['prefix' => 'api'], function () { - Route::get('/', [Admin\ApiController::class, 'index'])->name('admin.api.index'); - Route::get('/new', [Admin\ApiController::class, 'create'])->name('admin.api.new'); - - Route::post('/new', [Admin\ApiController::class, 'store']); - - Route::delete('/revoke/{identifier}', [Admin\ApiController::class, 'delete'])->name('admin.api.delete'); -}); - -/* -|-------------------------------------------------------------------------- -| Location Controller Routes -|-------------------------------------------------------------------------- -| -| Endpoint: /admin/locations -| -*/ -Route::group(['prefix' => 'locations'], function () { - Route::get('/', [Admin\LocationController::class, 'index'])->name('admin.locations'); - Route::get('/view/{location:id}', [Admin\LocationController::class, 'view'])->name('admin.locations.view'); - - Route::post('/', [Admin\LocationController::class, 'create']); - Route::patch('/view/{location:id}', [Admin\LocationController::class, 'update']); -}); - -/* -|-------------------------------------------------------------------------- -| Database Controller Routes -|-------------------------------------------------------------------------- -| -| Endpoint: /admin/databases -| -*/ -Route::group(['prefix' => 'databases'], function () { - Route::get('/', [Admin\DatabaseController::class, 'index'])->name('admin.databases'); - Route::get('/view/{host:id}', [Admin\DatabaseController::class, 'view'])->name('admin.databases.view'); - - Route::post('/', [Admin\DatabaseController::class, 'create']); - Route::patch('/view/{host:id}', [Admin\DatabaseController::class, 'update']); - Route::delete('/view/{host:id}', [Admin\DatabaseController::class, 'delete']); -}); - -/* -|-------------------------------------------------------------------------- -| Settings Controller Routes -|-------------------------------------------------------------------------- -| -| Endpoint: /admin/settings -| -*/ -Route::group(['prefix' => 'settings'], function () { - Route::get('/', [Admin\Settings\IndexController::class, 'index'])->name('admin.settings'); - Route::get('/mail', [Admin\Settings\MailController::class, 'index'])->name('admin.settings.mail'); - Route::get('/advanced', [Admin\Settings\AdvancedController::class, 'index'])->name('admin.settings.advanced'); - - Route::post('/mail/test', [Admin\Settings\MailController::class, 'test'])->name('admin.settings.mail.test'); - - Route::patch('/', [Admin\Settings\IndexController::class, 'update']); - Route::patch('/mail', [Admin\Settings\MailController::class, 'update']); - Route::patch('/advanced', [Admin\Settings\AdvancedController::class, 'update']); -}); - -/* -|-------------------------------------------------------------------------- -| User Controller Routes -|-------------------------------------------------------------------------- -| -| Endpoint: /admin/users -| -*/ -Route::group(['prefix' => 'users'], function () { - Route::get('/', [Admin\UserController::class, 'index'])->name('admin.users'); - Route::get('/accounts.json', [Admin\UserController::class, 'json'])->name('admin.users.json'); - Route::get('/new', [Admin\UserController::class, 'create'])->name('admin.users.new'); - Route::get('/view/{user:id}', [Admin\UserController::class, 'view'])->name('admin.users.view'); - - Route::post('/new', [Admin\UserController::class, 'store']); - - Route::patch('/view/{user:id}', [Admin\UserController::class, 'update']); - Route::delete('/view/{user:id}', [Admin\UserController::class, 'delete']); -}); - -/* -|-------------------------------------------------------------------------- -| Server Controller Routes -|-------------------------------------------------------------------------- -| -| Endpoint: /admin/servers -| -*/ -Route::group(['prefix' => 'servers'], function () { - Route::get('/', [Admin\Servers\ServerController::class, 'index'])->name('admin.servers'); - Route::get('/new', [Admin\Servers\CreateServerController::class, 'index'])->name('admin.servers.new'); - Route::get('/view/{server:id}', [Admin\Servers\ServerViewController::class, 'index'])->name('admin.servers.view'); - - Route::group(['middleware' => [ServerInstalled::class]], function () { - Route::get('/view/{server:id}/details', [Admin\Servers\ServerViewController::class, 'details'])->name('admin.servers.view.details'); - Route::get('/view/{server:id}/build', [Admin\Servers\ServerViewController::class, 'build'])->name('admin.servers.view.build'); - Route::get('/view/{server:id}/startup', [Admin\Servers\ServerViewController::class, 'startup'])->name('admin.servers.view.startup'); - Route::get('/view/{server:id}/database', [Admin\Servers\ServerViewController::class, 'database'])->name('admin.servers.view.database'); - Route::get('/view/{server:id}/mounts', [Admin\Servers\ServerViewController::class, 'mounts'])->name('admin.servers.view.mounts'); - }); - - Route::get('/view/{server:id}/manage', [Admin\Servers\ServerViewController::class, 'manage'])->name('admin.servers.view.manage'); - Route::get('/view/{server:id}/delete', [Admin\Servers\ServerViewController::class, 'delete'])->name('admin.servers.view.delete'); - - Route::post('/new', [Admin\Servers\CreateServerController::class, 'store']); - Route::post('/view/{server:id}/build', [Admin\ServersController::class, 'updateBuild']); - Route::post('/view/{server:id}/startup', [Admin\ServersController::class, 'saveStartup']); - Route::post('/view/{server:id}/database', [Admin\ServersController::class, 'newDatabase']); - Route::post('/view/{server:id}/mounts', [Admin\ServersController::class, 'addMount'])->name('admin.servers.view.mounts.store'); - Route::post('/view/{server:id}/manage/toggle', [Admin\ServersController::class, 'toggleInstall'])->name('admin.servers.view.manage.toggle'); - Route::post('/view/{server:id}/manage/suspension', [Admin\ServersController::class, 'manageSuspension'])->name('admin.servers.view.manage.suspension'); - Route::post('/view/{server:id}/manage/reinstall', [Admin\ServersController::class, 'reinstallServer'])->name('admin.servers.view.manage.reinstall'); - Route::post('/view/{server:id}/manage/transfer', [Admin\Servers\ServerTransferController::class, 'transfer'])->name('admin.servers.view.manage.transfer'); - Route::post('/view/{server:id}/delete', [Admin\ServersController::class, 'delete']); - - Route::patch('/view/{server:id}/details', [Admin\ServersController::class, 'setDetails']); - Route::patch('/view/{server:id}/database', [Admin\ServersController::class, 'resetDatabasePassword']); - - Route::delete('/view/{server:id}/database/{database:id}/delete', [Admin\ServersController::class, 'deleteDatabase'])->name('admin.servers.view.database.delete'); - Route::delete('/view/{server:id}/mounts/{mount:id}', [Admin\ServersController::class, 'deleteMount']) - ->name('admin.servers.view.mounts.delete'); -}); - -/* -|-------------------------------------------------------------------------- -| Node Controller Routes -|-------------------------------------------------------------------------- -| -| Endpoint: /admin/nodes -| -*/ -Route::group(['prefix' => 'nodes'], function () { - Route::get('/', [Admin\Nodes\NodeController::class, 'index'])->name('admin.nodes'); - Route::get('/new', [Admin\NodesController::class, 'create'])->name('admin.nodes.new'); - Route::get('/view/{node:id}', [Admin\Nodes\NodeViewController::class, 'index'])->name('admin.nodes.view'); - Route::get('/view/{node:id}/settings', [Admin\Nodes\NodeViewController::class, 'settings'])->name('admin.nodes.view.settings'); - Route::get('/view/{node:id}/configuration', [Admin\Nodes\NodeViewController::class, 'configuration'])->name('admin.nodes.view.configuration'); - Route::get('/view/{node:id}/allocation', [Admin\Nodes\NodeViewController::class, 'allocations'])->name('admin.nodes.view.allocation'); - Route::get('/view/{node:id}/servers', [Admin\Nodes\NodeViewController::class, 'servers'])->name('admin.nodes.view.servers'); - Route::get('/view/{node:id}/system-information', Admin\Nodes\SystemInformationController::class); - - Route::post('/new', [Admin\NodesController::class, 'store']); - Route::post('/view/{node:id}/allocation', [Admin\NodesController::class, 'createAllocation']); - Route::post('/view/{node:id}/allocation/remove', [Admin\NodesController::class, 'allocationRemoveBlock'])->name('admin.nodes.view.allocation.removeBlock'); - Route::post('/view/{node:id}/allocation/alias', [Admin\NodesController::class, 'allocationSetAlias'])->name('admin.nodes.view.allocation.setAlias'); - Route::post('/view/{node:id}/settings/token', Admin\NodeAutoDeployController::class)->name('admin.nodes.view.configuration.token'); - - Route::patch('/view/{node:id}/settings', [Admin\NodesController::class, 'updateSettings']); - - Route::delete('/view/{node:id}/delete', [Admin\NodesController::class, 'delete'])->name('admin.nodes.view.delete'); - Route::delete('/view/{node:id}/allocation/remove/{allocation:id}', [Admin\NodesController::class, 'allocationRemoveSingle'])->name('admin.nodes.view.allocation.removeSingle'); - Route::delete('/view/{node:id}/allocations', [Admin\NodesController::class, 'allocationRemoveMultiple'])->name('admin.nodes.view.allocation.removeMultiple'); -}); - -/* -|-------------------------------------------------------------------------- -| Mount Controller Routes -|-------------------------------------------------------------------------- -| -| Endpoint: /admin/mounts -| -*/ -Route::group(['prefix' => 'mounts'], function () { - Route::get('/', [Admin\MountController::class, 'index'])->name('admin.mounts'); - Route::get('/view/{mount:id}', [Admin\MountController::class, 'view'])->name('admin.mounts.view'); - - Route::post('/', [Admin\MountController::class, 'create']); - Route::post('/{mount:id}/eggs', [Admin\MountController::class, 'addEggs'])->name('admin.mounts.eggs'); - Route::post('/{mount:id}/nodes', [Admin\MountController::class, 'addNodes'])->name('admin.mounts.nodes'); - - Route::patch('/view/{mount:id}', [Admin\MountController::class, 'update']); - - Route::delete('/{mount:id}/eggs/{egg_id}', [Admin\MountController::class, 'deleteEgg']); - Route::delete('/{mount:id}/nodes/{node_id}', [Admin\MountController::class, 'deleteNode']); -}); - -/* -|-------------------------------------------------------------------------- -| Nest Controller Routes -|-------------------------------------------------------------------------- -| -| Endpoint: /admin/nests -| -*/ -Route::group(['prefix' => 'nests'], function () { - Route::get('/', [Admin\Nests\NestController::class, 'index'])->name('admin.nests'); - Route::get('/new', [Admin\Nests\NestController::class, 'create'])->name('admin.nests.new'); - Route::get('/view/{nest:id}', [Admin\Nests\NestController::class, 'view'])->name('admin.nests.view'); - Route::get('/egg/new', [Admin\Nests\EggController::class, 'create'])->name('admin.nests.egg.new'); - Route::get('/egg/{egg:id}', [Admin\Nests\EggController::class, 'view'])->name('admin.nests.egg.view'); - Route::get('/egg/{egg:id}/export', [Admin\Nests\EggShareController::class, 'export'])->name('admin.nests.egg.export'); - Route::get('/egg/{egg:id}/variables', [Admin\Nests\EggVariableController::class, 'view'])->name('admin.nests.egg.variables'); - Route::get('/egg/{egg:id}/scripts', [Admin\Nests\EggScriptController::class, 'index'])->name('admin.nests.egg.scripts'); - - Route::post('/new', [Admin\Nests\NestController::class, 'store']); - Route::post('/import', [Admin\Nests\EggShareController::class, 'import'])->name('admin.nests.egg.import'); - Route::post('/egg/new', [Admin\Nests\EggController::class, 'store']); - Route::post('/egg/{egg:id}/variables', [Admin\Nests\EggVariableController::class, 'store']); - - Route::put('/egg/{egg:id}', [Admin\Nests\EggShareController::class, 'update']); - - Route::patch('/view/{nest:id}', [Admin\Nests\NestController::class, 'update']); - Route::patch('/egg/{egg:id}', [Admin\Nests\EggController::class, 'update']); - Route::patch('/egg/{egg:id}/scripts', [Admin\Nests\EggScriptController::class, 'update']); - Route::patch('/egg/{egg:id}/variables/{variable:id}', [Admin\Nests\EggVariableController::class, 'update'])->name('admin.nests.egg.variables.edit'); - - Route::delete('/view/{nest:id}', [Admin\Nests\NestController::class, 'destroy']); - Route::delete('/egg/{egg:id}', [Admin\Nests\EggController::class, 'destroy']); - Route::delete('/egg/{egg:id}/variables/{variable:id}', [Admin\Nests\EggVariableController::class, 'destroy']); -}); +Route::get('/', [Admin\BaseController::class, 'index'])->name('admin.index')->fallback(); +Route::get('/{react}', [Admin\BaseController::class, 'index'])->where('react', '.+'); diff --git a/tests/Integration/Http/Controllers/Admin/UserControllerTest.php b/tests/Integration/Http/Controllers/Admin/UserControllerTest.php deleted file mode 100644 index 34cf9f938d..0000000000 --- a/tests/Integration/Http/Controllers/Admin/UserControllerTest.php +++ /dev/null @@ -1,59 +0,0 @@ -create(['username' => $unique . '_1']), - User::factory()->create(['username' => $unique . '_2']), - ]; - - $servers = [ - $this->createServerModel(['owner_id' => $users[0]->id]), - $this->createServerModel(['owner_id' => $users[0]->id]), - $this->createServerModel(['owner_id' => $users[0]->id]), - $this->createServerModel(['owner_id' => $users[1]->id]), - ]; - - Subuser::query()->forceCreate(['server_id' => $servers[0]->id, 'user_id' => $users[1]->id]); - Subuser::query()->forceCreate(['server_id' => $servers[1]->id, 'user_id' => $users[1]->id]); - - /** @var \Pterodactyl\Http\Controllers\Admin\UserController $controller */ - $controller = $this->app->make(UserController::class); - - $request = Request::create('/admin/users?filter[username]=' . $unique); - $this->app->instance(Request::class, $request); - - $data = $controller->index($request)->getData(); - $this->assertArrayHasKey('users', $data); - $this->assertInstanceOf(LengthAwarePaginator::class, $data['users']); - - /** @var \Pterodactyl\Models\User[] $response */ - $response = $data['users']->items(); - $this->assertCount(2, $response); - $this->assertInstanceOf(User::class, $response[0]); - $this->assertSame(3, (int) $response[0]->servers_count); - $this->assertSame(0, (int) $response[0]->subuser_of_count); - $this->assertSame(1, (int) $response[1]->servers_count); - $this->assertSame(2, (int) $response[1]->subuser_of_count); - } -} From 67bf3e342eebef04931e3c814ae2811d9f460bee Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Wed, 14 Dec 2022 17:05:46 -0700 Subject: [PATCH 333/458] api(application): v2 backport --- app/Exceptions/Handler.php | 2 +- .../QueryValueOutOfRangeHttpException.php | 21 ++ .../ManifestDoesNotExistException.php | 14 -- .../Service/Egg/BadYamlFormatException.php | 9 + .../ManifestDoesNotExistSolution.php | 25 --- .../InvalidTransformerLevelException.php | 9 - .../Application/ApplicationApiController.php | 21 +- .../Databases/DatabaseController.php | 99 +++++++++ .../Api/Application/Eggs/EggController.php | 120 +++++++++++ .../Eggs/EggVariableController.php | 75 +++++++ .../Locations/LocationController.php | 25 +-- .../Application/Mounts/MountController.php | 163 +++++++++++++++ .../Api/Application/Nests/EggController.php | 33 --- .../Api/Application/Nests/NestController.php | 97 ++++++++- .../Nodes/AllocationController.php | 37 ++-- .../Nodes/NodeConfigurationController.php | 6 +- .../Api/Application/Nodes/NodeController.php | 31 +-- .../Nodes/NodeDeploymentController.php | 4 +- .../Nodes/NodeInformationController.php | 47 +++++ .../Api/Application/Roles/RoleController.php | 96 +++++++++ .../Servers/DatabaseController.php | 22 +- .../Servers/ExternalServerController.php | 2 +- .../Application/Servers/ServerController.php | 49 ++++- .../Servers/ServerDetailsController.php | 16 +- .../Servers/ServerManagementController.php | 6 +- .../Application/Servers/StartupController.php | 7 +- .../Users/ExternalUserController.php | 2 +- .../Api/Application/Users/UserController.php | 65 ++++-- .../Api/Application/VersionController.php | 25 +++ .../Api/Client/AccountController.php | 2 +- .../Api/Client/ActivityLogController.php | 2 +- .../Api/Client/ApiKeyController.php | 4 +- .../Api/Client/ClientApiController.php | 22 +- .../Api/Client/ClientController.php | 2 +- .../Api/Client/SSHKeyController.php | 4 +- .../Client/Servers/ActivityLogController.php | 2 +- .../Api/Client/Servers/BackupController.php | 8 +- .../Api/Client/Servers/DatabaseController.php | 6 +- .../Api/Client/Servers/FileController.php | 4 +- .../Servers/NetworkAllocationController.php | 8 +- .../Servers/ResourceUtilizationController.php | 2 +- .../Api/Client/Servers/ScheduleController.php | 8 +- .../Client/Servers/ScheduleTaskController.php | 4 +- .../Api/Client/Servers/ServerController.php | 2 +- .../Api/Client/Servers/StartupController.php | 4 +- .../Api/Client/Servers/SubuserController.php | 8 +- .../Auth/AbstractLoginController.php | 2 +- app/Http/Requests/Api/ApiRequest.php | 83 ++++++++ .../Allocations/DeleteAllocationRequest.php | 4 - .../Allocations/GetAllocationsRequest.php | 4 - .../Allocations/StoreAllocationRequest.php | 18 +- .../Api/Application/ApplicationApiRequest.php | 88 +------- .../Databases/DeleteDatabaseRequest.php | 9 + .../Databases/GetDatabaseRequest.php | 7 + .../Databases/GetDatabasesRequest.php | 9 + .../Databases/StoreDatabaseRequest.php | 14 ++ .../Databases/UpdateDatabaseRequest.php | 13 ++ .../Api/Application/Eggs/DeleteEggRequest.php | 16 ++ .../Api/Application/Eggs/ExportEggRequest.php | 9 + .../Api/Application/Eggs/GetEggRequest.php | 7 + .../Api/Application/Eggs/GetEggsRequest.php | 9 + .../Api/Application/Eggs/ImportEggRequest.php | 9 + .../Api/Application/Eggs/StoreEggRequest.php | 30 +++ .../Api/Application/Eggs/UpdateEggRequest.php | 28 +++ .../Variables/StoreEggVariableRequest.php | 22 ++ .../Variables/UpdateEggVariablesRequest.php | 24 +++ .../Locations/DeleteLocationRequest.php | 4 - .../Locations/GetLocationsRequest.php | 4 - .../Locations/StoreLocationRequest.php | 11 - .../Locations/UpdateLocationRequest.php | 7 +- .../Application/Mounts/DeleteMountRequest.php | 9 + .../Application/Mounts/GetMountRequest.php | 7 + .../Application/Mounts/GetMountsRequest.php | 9 + .../Application/Mounts/MountEggsRequest.php | 13 ++ .../Application/Mounts/MountNodesRequest.php | 13 ++ .../Application/Mounts/StoreMountRequest.php | 14 ++ .../Application/Mounts/UpdateMountRequest.php | 13 ++ .../Application/Nests/DeleteNestRequest.php | 9 + .../Application/Nests/Eggs/GetEggRequest.php | 13 -- .../Application/Nests/Eggs/GetEggsRequest.php | 13 -- .../Api/Application/Nests/GetNestRequest.php | 7 + .../Api/Application/Nests/GetNestsRequest.php | 4 - .../Application/Nests/StoreNestRequest.php | 14 ++ .../Application/Nests/UpdateNestRequest.php | 13 ++ .../Application/Nodes/DeleteNodeRequest.php | 4 - .../Api/Application/Nodes/GetNodesRequest.php | 4 - .../Application/Nodes/StoreNodeRequest.php | 43 ++-- .../Application/Nodes/UpdateNodeRequest.php | 9 +- .../Application/Roles/DeleteRoleRequest.php | 9 + .../Api/Application/Roles/GetRoleRequest.php | 7 + .../Api/Application/Roles/GetRolesRequest.php | 9 + .../Application/Roles/StoreRoleRequest.php | 14 ++ .../Application/Roles/UpdateRoleRequest.php | 13 ++ .../Databases/GetServerDatabaseRequest.php | 4 - .../Databases/GetServerDatabasesRequest.php | 4 - .../Databases/ServerDatabaseWriteRequest.php | 3 - .../Databases/StoreServerDatabaseRequest.php | 28 +-- .../Servers/GetExternalServerRequest.php | 4 - .../Application/Servers/GetServerRequest.php | 4 - .../Servers/ServerWriteRequest.php | 4 - .../Servers/StoreServerRequest.php | 132 ++++-------- .../UpdateServerBuildConfigurationRequest.php | 24 ++- .../Servers/UpdateServerDetailsRequest.php | 14 +- .../Servers/UpdateServerRequest.php | 77 +++++++ .../Servers/UpdateServerStartupRequest.php | 29 +-- .../Application/Users/DeleteUserRequest.php | 4 - .../Users/GetExternalUserRequest.php | 4 - .../Api/Application/Users/GetUserRequest.php | 7 + .../Api/Application/Users/GetUsersRequest.php | 4 - .../Application/Users/StoreUserRequest.php | 42 +--- .../Application/Users/UpdateUserRequest.php | 7 +- app/Models/AdminRole.php | 68 ++++++ app/Models/AuditLog.php | 80 -------- app/Models/Backup.php | 1 - app/Models/Server.php | 3 +- app/Models/User.php | 41 +++- app/Policies/.gitkeep | 0 app/Providers/AppServiceProvider.php | 33 --- app/Services/Eggs/EggParserService.php | 10 +- .../Eggs/Sharing/EggImporterService.php | 91 +++++++- .../Eggs/Sharing/EggUpdateImporterService.php | 20 +- .../Eggs/Variables/VariableUpdateService.php | 22 +- .../Helpers/SoftwareVersionService.php | 103 ++++++++-- .../Servers/BuildModificationService.php | 2 +- .../ServerConfigurationStructureService.php | 9 +- .../Api/Application/AdminRoleTransformer.php | 29 +++ .../Api/Application/AllocationTransformer.php | 39 ++-- .../Api/Application/BaseTransformer.php | 114 ---------- .../Application/DatabaseHostTransformer.php | 21 +- .../Api/Application/EggTransformer.php | 90 +++----- .../Application/EggVariableTransformer.php | 8 +- .../Api/Application/LocationTransformer.php | 35 ++-- .../Api/Application/MountTransformer.php | 75 +++++++ .../Api/Application/NestTransformer.php | 25 +-- .../Api/Application/NodeTransformer.php | 49 ++--- .../Application/ServerDatabaseTransformer.php | 48 ++--- .../Api/Application/ServerTransformer.php | 117 ++++------- .../Application/ServerVariableTransformer.php | 14 +- .../Api/Application/SubuserTransformer.php | 43 ++-- .../Api/Application/UserTransformer.php | 36 ++-- .../Api/Client/AccountTransformer.php | 3 +- .../Api/Client/ActivityLogTransformer.php | 9 +- .../Api/Client/AllocationTransformer.php | 3 +- .../Api/Client/ApiKeyTransformer.php | 3 +- .../Api/Client/BackupTransformer.php | 23 ++- .../Api/Client/BaseClientTransformer.php | 43 ---- .../Api/Client/DatabaseTransformer.php | 10 +- .../Api/Client/EggTransformer.php | 9 +- .../Api/Client/EggVariableTransformer.php | 21 +- .../Api/Client/FileObjectTransformer.php | 33 +-- .../Api/Client/ScheduleTransformer.php | 20 +- .../Api/Client/ServerTransformer.php | 32 +-- .../Api/Client/StatsTransformer.php | 21 +- .../Api/Client/SubuserTransformer.php | 7 +- .../Api/Client/TaskTransformer.php | 7 +- .../Api/Client/UserSSHKeyTransformer.php | 5 +- .../Api/Client/UserTransformer.php | 7 +- app/Transformers/Api/Transformer.php | 156 ++++++++++++++ database/Seeders/EggSeeder.php | 16 +- ..._09_25_021109_create_admin_roles_table.php | 31 +++ ...dd_admin_role_id_column_to_users_table.php | 30 +++ ...database_host_id_column_to_nodes_table.php | 41 ++++ ...658_change_port_columns_on_nodes_table.php | 58 ++++++ ..._29_032255_yeet_names_from_users_table.php | 28 +++ ...rop_config_logs_column_from_eggs_table.php | 27 +++ ..._202643_update_default_values_for_eggs.php | 28 +++ ...tartup_field_nullable_on_servers_table.php | 27 +++ resources/views/templates/wrapper.blade.php | 2 +- routes/api-application.php | 194 +++++++++++++----- .../ApplicationApiIntegrationTestCase.php | 19 +- tests/Integration/IntegrationTestCase.php | 5 +- tests/Traits/Http/MocksMiddlewareClosure.php | 2 +- 172 files changed, 2899 insertions(+), 1556 deletions(-) create mode 100644 app/Exceptions/Http/QueryValueOutOfRangeHttpException.php delete mode 100644 app/Exceptions/ManifestDoesNotExistException.php create mode 100644 app/Exceptions/Service/Egg/BadYamlFormatException.php delete mode 100644 app/Exceptions/Solutions/ManifestDoesNotExistSolution.php delete mode 100644 app/Exceptions/Transformer/InvalidTransformerLevelException.php create mode 100644 app/Http/Controllers/Api/Application/Databases/DatabaseController.php create mode 100644 app/Http/Controllers/Api/Application/Eggs/EggController.php create mode 100644 app/Http/Controllers/Api/Application/Eggs/EggVariableController.php create mode 100644 app/Http/Controllers/Api/Application/Mounts/MountController.php delete mode 100644 app/Http/Controllers/Api/Application/Nests/EggController.php create mode 100644 app/Http/Controllers/Api/Application/Nodes/NodeInformationController.php create mode 100644 app/Http/Controllers/Api/Application/Roles/RoleController.php create mode 100644 app/Http/Controllers/Api/Application/VersionController.php create mode 100644 app/Http/Requests/Api/ApiRequest.php create mode 100644 app/Http/Requests/Api/Application/Databases/DeleteDatabaseRequest.php create mode 100644 app/Http/Requests/Api/Application/Databases/GetDatabaseRequest.php create mode 100644 app/Http/Requests/Api/Application/Databases/GetDatabasesRequest.php create mode 100644 app/Http/Requests/Api/Application/Databases/StoreDatabaseRequest.php create mode 100644 app/Http/Requests/Api/Application/Databases/UpdateDatabaseRequest.php create mode 100644 app/Http/Requests/Api/Application/Eggs/DeleteEggRequest.php create mode 100644 app/Http/Requests/Api/Application/Eggs/ExportEggRequest.php create mode 100644 app/Http/Requests/Api/Application/Eggs/GetEggRequest.php create mode 100644 app/Http/Requests/Api/Application/Eggs/GetEggsRequest.php create mode 100644 app/Http/Requests/Api/Application/Eggs/ImportEggRequest.php create mode 100644 app/Http/Requests/Api/Application/Eggs/StoreEggRequest.php create mode 100644 app/Http/Requests/Api/Application/Eggs/UpdateEggRequest.php create mode 100644 app/Http/Requests/Api/Application/Eggs/Variables/StoreEggVariableRequest.php create mode 100644 app/Http/Requests/Api/Application/Eggs/Variables/UpdateEggVariablesRequest.php create mode 100644 app/Http/Requests/Api/Application/Mounts/DeleteMountRequest.php create mode 100644 app/Http/Requests/Api/Application/Mounts/GetMountRequest.php create mode 100644 app/Http/Requests/Api/Application/Mounts/GetMountsRequest.php create mode 100644 app/Http/Requests/Api/Application/Mounts/MountEggsRequest.php create mode 100644 app/Http/Requests/Api/Application/Mounts/MountNodesRequest.php create mode 100644 app/Http/Requests/Api/Application/Mounts/StoreMountRequest.php create mode 100644 app/Http/Requests/Api/Application/Mounts/UpdateMountRequest.php create mode 100644 app/Http/Requests/Api/Application/Nests/DeleteNestRequest.php delete mode 100644 app/Http/Requests/Api/Application/Nests/Eggs/GetEggRequest.php delete mode 100644 app/Http/Requests/Api/Application/Nests/Eggs/GetEggsRequest.php create mode 100644 app/Http/Requests/Api/Application/Nests/GetNestRequest.php create mode 100644 app/Http/Requests/Api/Application/Nests/StoreNestRequest.php create mode 100644 app/Http/Requests/Api/Application/Nests/UpdateNestRequest.php create mode 100644 app/Http/Requests/Api/Application/Roles/DeleteRoleRequest.php create mode 100644 app/Http/Requests/Api/Application/Roles/GetRoleRequest.php create mode 100644 app/Http/Requests/Api/Application/Roles/GetRolesRequest.php create mode 100644 app/Http/Requests/Api/Application/Roles/StoreRoleRequest.php create mode 100644 app/Http/Requests/Api/Application/Roles/UpdateRoleRequest.php create mode 100644 app/Http/Requests/Api/Application/Servers/UpdateServerRequest.php create mode 100644 app/Http/Requests/Api/Application/Users/GetUserRequest.php create mode 100644 app/Models/AdminRole.php delete mode 100644 app/Models/AuditLog.php delete mode 100644 app/Policies/.gitkeep create mode 100644 app/Transformers/Api/Application/AdminRoleTransformer.php delete mode 100644 app/Transformers/Api/Application/BaseTransformer.php create mode 100644 app/Transformers/Api/Application/MountTransformer.php delete mode 100644 app/Transformers/Api/Client/BaseClientTransformer.php create mode 100644 app/Transformers/Api/Transformer.php create mode 100644 database/migrations/2020_09_25_021109_create_admin_roles_table.php create mode 100644 database/migrations/2021_01_16_201057_add_admin_role_id_column_to_users_table.php create mode 100644 database/migrations/2021_01_30_193343_add_database_host_id_column_to_nodes_table.php create mode 100644 database/migrations/2021_02_23_212658_change_port_columns_on_nodes_table.php create mode 100644 database/migrations/2021_07_29_032255_yeet_names_from_users_table.php create mode 100644 database/migrations/2021_10_23_185304_drop_config_logs_column_from_eggs_table.php create mode 100644 database/migrations/2021_10_23_202643_update_default_values_for_eggs.php create mode 100644 database/migrations/2021_11_01_180130_make_startup_field_nullable_on_servers_table.php diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index 6fd1b5d8e0..4cfee8e461 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -233,7 +233,7 @@ protected function convertExceptionToArray(\Throwable $e, array $override = []): /** * Return an array of exceptions that should not be reported. */ - public static function isReportable(\Exception $exception): bool + public static function isReportable(Exception $exception): bool { return (new static(Container::getInstance()))->shouldReport($exception); } diff --git a/app/Exceptions/Http/QueryValueOutOfRangeHttpException.php b/app/Exceptions/Http/QueryValueOutOfRangeHttpException.php new file mode 100644 index 0000000000..4610f0e2e0 --- /dev/null +++ b/app/Exceptions/Http/QueryValueOutOfRangeHttpException.php @@ -0,0 +1,21 @@ + 'https://github.com/pterodactyl/panel/blob/develop/package.json', - ]; - } -} diff --git a/app/Exceptions/Transformer/InvalidTransformerLevelException.php b/app/Exceptions/Transformer/InvalidTransformerLevelException.php deleted file mode 100644 index 3d4c24248a..0000000000 --- a/app/Exceptions/Transformer/InvalidTransformerLevelException.php +++ /dev/null @@ -1,9 +0,0 @@ - $abstract - * - * @return T - * - * @noinspection PhpDocSignatureInspection + * Return an HTTP/201 response for the API. */ - public function getTransformer(string $abstract) + protected function returnAccepted(): Response { - Assert::subclassOf($abstract, BaseTransformer::class); - - return $abstract::fromRequest($this->request); + return new Response('', Response::HTTP_ACCEPTED); } /** diff --git a/app/Http/Controllers/Api/Application/Databases/DatabaseController.php b/app/Http/Controllers/Api/Application/Databases/DatabaseController.php new file mode 100644 index 0000000000..2acd2fa183 --- /dev/null +++ b/app/Http/Controllers/Api/Application/Databases/DatabaseController.php @@ -0,0 +1,99 @@ +query('per_page', '10'); + if ($perPage < 1 || $perPage > 100) { + throw new QueryValueOutOfRangeHttpException('per_page', 1, 100); + } + + $databases = QueryBuilder::for(DatabaseHost::query()) + ->allowedFilters(['name', 'host']) + ->allowedSorts(['id', 'name', 'host']) + ->paginate($perPage); + + return $this->fractal->collection($databases) + ->transformWith(DatabaseHostTransformer::class) + ->toArray(); + } + + /** + * Returns a single database host. + */ + public function view(GetDatabaseRequest $request, DatabaseHost $databaseHost): array + { + return $this->fractal->item($databaseHost) + ->transformWith(DatabaseHostTransformer::class) + ->toArray(); + } + + /** + * Creates a new database host. + * + * @throws \Throwable + */ + public function store(StoreDatabaseRequest $request): JsonResponse + { + $databaseHost = $this->creationService->handle($request->validated()); + + return $this->fractal->item($databaseHost) + ->transformWith(DatabaseHostTransformer::class) + ->respond(JsonResponse::HTTP_CREATED); + } + + /** + * Updates a database host. + * + * @throws \Throwable + */ + public function update(UpdateDatabaseRequest $request, DatabaseHost $databaseHost): array + { + $databaseHost = $this->updateService->handle($databaseHost->id, $request->validated()); + + return $this->fractal->item($databaseHost) + ->transformWith(DatabaseHostTransformer::class) + ->toArray(); + } + + /** + * Deletes a database host. + * + * @throws \Exception + */ + public function delete(DeleteDatabaseRequest $request, DatabaseHost $databaseHost): Response + { + $databaseHost->delete(); + + return $this->returnNoContent(); + } +} diff --git a/app/Http/Controllers/Api/Application/Eggs/EggController.php b/app/Http/Controllers/Api/Application/Eggs/EggController.php new file mode 100644 index 0000000000..96c0b8a4fd --- /dev/null +++ b/app/Http/Controllers/Api/Application/Eggs/EggController.php @@ -0,0 +1,120 @@ +eggExporterService = $eggExporterService; + } + + /** + * Return an array of all eggs on a given nest. + */ + public function index(GetEggsRequest $request, Nest $nest): array + { + $perPage = (int) $request->query('per_page', '10'); + if ($perPage > 100) { + throw new QueryValueOutOfRangeHttpException('per_page', 1, 100); + } + + // @phpstan-ignore-next-line + $eggs = QueryBuilder::for(Egg::query()) + ->where('nest_id', '=', $nest->id) + ->allowedFilters(['id', 'name', 'author']) + ->allowedSorts(['id', 'name', 'author']); + if ($perPage > 0) { + $eggs = $eggs->paginate($perPage); + } + + return $this->fractal->collection($eggs) + ->transformWith(EggTransformer::class) + ->toArray(); + } + + /** + * Returns a single egg. + */ + public function view(GetEggRequest $request, Egg $egg): array + { + return $this->fractal->item($egg) + ->transformWith(EggTransformer::class) + ->toArray(); + } + + /** + * Creates a new egg. + */ + public function store(StoreEggRequest $request): JsonResponse + { + $validated = $request->validated(); + $merged = array_merge($validated, [ + 'uuid' => Uuid::uuid4()->toString(), + // TODO: allow this to be set in the request, and default to config value if null or not present. + 'author' => config('pterodactyl.service.author'), + ]); + + $egg = Egg::query()->create($merged); + + return $this->fractal->item($egg) + ->transformWith(EggTransformer::class) + ->respond(Response::HTTP_CREATED); + } + + /** + * Updates an egg. + */ + public function update(UpdateEggRequest $request, Egg $egg): array + { + $egg->update($request->validated()); + + return $this->fractal->item($egg) + ->transformWith(EggTransformer::class) + ->toArray(); + } + + /** + * Deletes an egg. + * + * @throws \Exception + */ + public function delete(DeleteEggRequest $request, Egg $egg): Response + { + $egg->delete(); + + return $this->returnNoContent(); + } + + /** + * Exports an egg. + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function export(ExportEggRequest $request, int $eggId): JsonResponse + { + return new JsonResponse($this->eggExporterService->handle($eggId)); + } +} diff --git a/app/Http/Controllers/Api/Application/Eggs/EggVariableController.php b/app/Http/Controllers/Api/Application/Eggs/EggVariableController.php new file mode 100644 index 0000000000..c837244d78 --- /dev/null +++ b/app/Http/Controllers/Api/Application/Eggs/EggVariableController.php @@ -0,0 +1,75 @@ +variableCreationService->handle($egg->id, $request->validated()); + + return $this->fractal->item($variable) + ->transformWith(EggVariableTransformer::class) + ->toArray(); + } + + /** + * Updates multiple egg variables. + * + * @throws \Throwable + */ + public function update(UpdateEggVariablesRequest $request, Egg $egg): array + { + $validated = $request->validated(); + + $this->connection->transaction(function () use ($egg, $validated) { + foreach ($validated as $data) { + $this->variableUpdateService->handle($egg, $data); + } + }); + + return $this->fractal->collection($egg->refresh()->variables) + ->transformWith(EggVariableTransformer::class) + ->toArray(); + } + + /** + * Deletes a single egg variable. + */ + public function delete(Request $request, Egg $egg, EggVariable $eggVariable): Response + { + EggVariable::query() + ->where('id', $eggVariable->id) + ->where('egg_id', $egg->id) + ->delete(); + + return $this->returnNoContent(); + } +} diff --git a/app/Http/Controllers/Api/Application/Locations/LocationController.php b/app/Http/Controllers/Api/Application/Locations/LocationController.php index b95a0776a9..337f5e76d6 100644 --- a/app/Http/Controllers/Api/Application/Locations/LocationController.php +++ b/app/Http/Controllers/Api/Application/Locations/LocationController.php @@ -10,6 +10,7 @@ use Pterodactyl\Services\Locations\LocationCreationService; use Pterodactyl\Services\Locations\LocationDeletionService; use Pterodactyl\Transformers\Api\Application\LocationTransformer; +use Pterodactyl\Exceptions\Http\QueryValueOutOfRangeHttpException; use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController; use Pterodactyl\Http\Requests\Api\Application\Locations\GetLocationRequest; use Pterodactyl\Http\Requests\Api\Application\Locations\GetLocationsRequest; @@ -35,13 +36,18 @@ public function __construct( */ public function index(GetLocationsRequest $request): array { + $perPage = (int) $request->query('per_page', '10'); + if ($perPage < 1 || $perPage > 100) { + throw new QueryValueOutOfRangeHttpException('per_page', 1, 100); + } + $locations = QueryBuilder::for(Location::query()) ->allowedFilters(['short', 'long']) - ->allowedSorts(['id']) - ->paginate($request->query('per_page') ?? 50); + ->allowedSorts(['id', 'short', 'long']) + ->paginate($perPage); return $this->fractal->collection($locations) - ->transformWith($this->getTransformer(LocationTransformer::class)) + ->transformWith(LocationTransformer::class) ->toArray(); } @@ -51,7 +57,7 @@ public function index(GetLocationsRequest $request): array public function view(GetLocationRequest $request, Location $location): array { return $this->fractal->item($location) - ->transformWith($this->getTransformer(LocationTransformer::class)) + ->transformWith(LocationTransformer::class) ->toArray(); } @@ -66,12 +72,7 @@ public function store(StoreLocationRequest $request): JsonResponse $location = $this->creationService->handle($request->validated()); return $this->fractal->item($location) - ->transformWith($this->getTransformer(LocationTransformer::class)) - ->addMeta([ - 'resource' => route('api.application.locations.view', [ - 'location' => $location->id, - ]), - ]) + ->transformWith(LocationTransformer::class) ->respond(201); } @@ -86,7 +87,7 @@ public function update(UpdateLocationRequest $request, Location $location): arra $location = $this->updateService->handle($location, $request->validated()); return $this->fractal->item($location) - ->transformWith($this->getTransformer(LocationTransformer::class)) + ->transformWith(LocationTransformer::class) ->toArray(); } @@ -99,6 +100,6 @@ public function delete(DeleteLocationRequest $request, Location $location): Resp { $this->deletionService->handle($location); - return response('', 204); + return $this->returnNoContent(); } } diff --git a/app/Http/Controllers/Api/Application/Mounts/MountController.php b/app/Http/Controllers/Api/Application/Mounts/MountController.php new file mode 100644 index 0000000000..6606004fd6 --- /dev/null +++ b/app/Http/Controllers/Api/Application/Mounts/MountController.php @@ -0,0 +1,163 @@ +query('per_page', '10'); + if ($perPage < 1 || $perPage > 100) { + throw new QueryValueOutOfRangeHttpException('per_page', 1, 100); + } + + $mounts = QueryBuilder::for(Mount::query()) + ->allowedFilters(['id', 'name', 'source', 'target']) + ->allowedSorts(['id', 'name', 'source', 'target']) + ->paginate($perPage); + + return $this->fractal->collection($mounts) + ->transformWith(MountTransformer::class) + ->toArray(); + } + + /** + * Returns a single mount. + */ + public function view(GetMountRequest $request, Mount $mount): array + { + return $this->fractal->item($mount) + ->transformWith(MountTransformer::class) + ->toArray(); + } + + /** + * Creates a new mount. + */ + public function store(StoreMountRequest $request): JsonResponse + { + $mount = Mount::query()->create($request->validated()); + + return $this->fractal->item($mount) + ->transformWith(MountTransformer::class) + ->respond(JsonResponse::HTTP_CREATED); + } + + /** + * Updates a mount. + */ + public function update(UpdateMountRequest $request, Mount $mount): array + { + $mount->update($request->validated()); + + return $this->fractal->item($mount) + ->transformWith(MountTransformer::class) + ->toArray(); + } + + /** + * Deletes a mount. + * + * @throws \Exception + */ + public function delete(DeleteMountRequest $request, Mount $mount): Response + { + $mount->delete(); + + return $this->returnNoContent(); + } + + /** + * Attaches eggs to a mount. + */ + public function addEggs(MountEggsRequest $request, Mount $mount): array + { + $data = $request->validated(); + + $eggs = $data['eggs'] ?? []; + if (count($eggs) > 0) { + $mount->eggs()->syncWithoutDetaching($eggs); + } + + return $this->fractal->item($mount) + ->transformWith(MountTransformer::class) + ->toArray(); + } + + /** + * Attaches nodes to a mount. + */ + public function addNodes(MountNodesRequest $request, Mount $mount): array + { + $data = $request->validated(); + + $nodes = $data['nodes'] ?? []; + if (count($nodes) > 0) { + $mount->nodes()->syncWithoutDetaching($nodes); + } + + return $this->fractal->item($mount) + ->transformWith(MountTransformer::class) + ->toArray(); + } + + /** + * Detaches eggs from a mount. + */ + public function deleteEggs(MountEggsRequest $request, Mount $mount): array + { + $data = $request->validated(); + + $eggs = $data['eggs'] ?? []; + if (count($eggs) > 0) { + $mount->eggs()->detach($eggs); + } + + return $this->fractal->item($mount) + ->transformWith(MountTransformer::class) + ->toArray(); + } + + /** + * Detaches nodes from a mount. + */ + public function deleteNodes(MountNodesRequest $request, Mount $mount): array + { + $data = $request->validated(); + + $nodes = $data['nodes'] ?? []; + if (count($nodes) > 0) { + $mount->nodes()->detach($nodes); + } + + return $this->fractal->item($mount) + ->transformWith(MountTransformer::class) + ->toArray(); + } +} diff --git a/app/Http/Controllers/Api/Application/Nests/EggController.php b/app/Http/Controllers/Api/Application/Nests/EggController.php deleted file mode 100644 index 83c3f77a0e..0000000000 --- a/app/Http/Controllers/Api/Application/Nests/EggController.php +++ /dev/null @@ -1,33 +0,0 @@ -fractal->collection($nest->eggs) - ->transformWith($this->getTransformer(EggTransformer::class)) - ->toArray(); - } - - /** - * Return a single egg that exists on the specified nest. - */ - public function view(GetEggRequest $request, Nest $nest, Egg $egg): array - { - return $this->fractal->item($egg) - ->transformWith($this->getTransformer(EggTransformer::class)) - ->toArray(); - } -} diff --git a/app/Http/Controllers/Api/Application/Nests/NestController.php b/app/Http/Controllers/Api/Application/Nests/NestController.php index f0044f53c9..708f591958 100644 --- a/app/Http/Controllers/Api/Application/Nests/NestController.php +++ b/app/Http/Controllers/Api/Application/Nests/NestController.php @@ -3,9 +3,21 @@ namespace Pterodactyl\Http\Controllers\Api\Application\Nests; use Pterodactyl\Models\Nest; -use Pterodactyl\Contracts\Repository\NestRepositoryInterface; +use Illuminate\Http\Response; +use Spatie\QueryBuilder\QueryBuilder; +use Pterodactyl\Services\Nests\NestUpdateService; +use Pterodactyl\Services\Nests\NestCreationService; +use Pterodactyl\Services\Nests\NestDeletionService; +use Pterodactyl\Services\Eggs\Sharing\EggImporterService; +use Pterodactyl\Transformers\Api\Application\EggTransformer; use Pterodactyl\Transformers\Api\Application\NestTransformer; +use Pterodactyl\Exceptions\Http\QueryValueOutOfRangeHttpException; +use Pterodactyl\Http\Requests\Api\Application\Nests\GetNestRequest; +use Pterodactyl\Http\Requests\Api\Application\Eggs\ImportEggRequest; use Pterodactyl\Http\Requests\Api\Application\Nests\GetNestsRequest; +use Pterodactyl\Http\Requests\Api\Application\Nests\StoreNestRequest; +use Pterodactyl\Http\Requests\Api\Application\Nests\DeleteNestRequest; +use Pterodactyl\Http\Requests\Api\Application\Nests\UpdateNestRequest; use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController; class NestController extends ApplicationApiController @@ -13,8 +25,12 @@ class NestController extends ApplicationApiController /** * NestController constructor. */ - public function __construct(private NestRepositoryInterface $repository) - { + public function __construct( + private NestCreationService $nestCreationService, + private NestDeletionService $nestDeletionService, + private NestUpdateService $nestUpdateService, + private EggImporterService $eggImporterService + ) { parent::__construct(); } @@ -23,20 +39,87 @@ public function __construct(private NestRepositoryInterface $repository) */ public function index(GetNestsRequest $request): array { - $nests = $this->repository->paginated($request->query('per_page') ?? 50); + $perPage = (int) $request->query('per_page', '10'); + if ($perPage > 100) { + throw new QueryValueOutOfRangeHttpException('per_page', 1, 100); + } + + $nests = QueryBuilder::for(Nest::query()) + ->allowedFilters(['id', 'name', 'author']) + ->allowedSorts(['id', 'name', 'author']); + if ($perPage > 0) { + $nests = $nests->paginate($perPage); + } return $this->fractal->collection($nests) - ->transformWith($this->getTransformer(NestTransformer::class)) + ->transformWith(NestTransformer::class) ->toArray(); } /** * Return information about a single Nest model. */ - public function view(GetNestsRequest $request, Nest $nest): array + public function view(GetNestRequest $request, Nest $nest): array + { + return $this->fractal->item($nest) + ->transformWith(NestTransformer::class) + ->toArray(); + } + + /** + * Creates a new nest. + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + */ + public function store(StoreNestRequest $request): array { + $nest = $this->nestCreationService->handle($request->validated()); + return $this->fractal->item($nest) - ->transformWith($this->getTransformer(NestTransformer::class)) + ->transformWith(NestTransformer::class) ->toArray(); } + + /** + * Imports an egg. + */ + public function import(ImportEggRequest $request, Nest $nest): array + { + $egg = $this->eggImporterService->handleContent( + $nest->id, + $request->getContent(), + $request->headers->get('Content-Type'), + ); + + return $this->fractal->item($egg) + ->transformWith(EggTransformer::class) + ->toArray(); + } + + /** + * Updates an existing nest. + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function update(UpdateNestRequest $request, Nest $nest): array + { + $this->nestUpdateService->handle($nest->id, $request->validated()); + + return $this->fractal->item($nest) + ->transformWith(NestTransformer::class) + ->toArray(); + } + + /** + * Deletes an existing nest. + * + * @throws \Pterodactyl\Exceptions\Service\HasActiveServersException + */ + public function delete(DeleteNestRequest $request, Nest $nest): Response + { + $this->nestDeletionService->handle($nest->id); + + return $this->returnNoContent(); + } } diff --git a/app/Http/Controllers/Api/Application/Nodes/AllocationController.php b/app/Http/Controllers/Api/Application/Nodes/AllocationController.php index 77404945a4..41483bf417 100644 --- a/app/Http/Controllers/Api/Application/Nodes/AllocationController.php +++ b/app/Http/Controllers/Api/Application/Nodes/AllocationController.php @@ -3,13 +3,14 @@ namespace Pterodactyl\Http\Controllers\Api\Application\Nodes; use Pterodactyl\Models\Node; -use Illuminate\Http\JsonResponse; +use Illuminate\Http\Response; use Pterodactyl\Models\Allocation; use Spatie\QueryBuilder\QueryBuilder; use Spatie\QueryBuilder\AllowedFilter; use Illuminate\Database\Eloquent\Builder; use Pterodactyl\Services\Allocations\AssignmentService; use Pterodactyl\Services\Allocations\AllocationDeletionService; +use Pterodactyl\Exceptions\Http\QueryValueOutOfRangeHttpException; use Pterodactyl\Transformers\Api\Application\AllocationTransformer; use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController; use Pterodactyl\Http\Requests\Api\Application\Allocations\GetAllocationsRequest; @@ -33,23 +34,27 @@ public function __construct( */ public function index(GetAllocationsRequest $request, Node $node): array { - $allocations = QueryBuilder::for($node->allocations()) + $perPage = (int) $request->query('per_page', '10'); + if ($perPage < 1 || $perPage > 100) { + throw new QueryValueOutOfRangeHttpException('per_page', 1, 100); + } + + $allocations = QueryBuilder::for(Allocation::query()->where('node_id', '=', $node->id)) ->allowedFilters([ - AllowedFilter::exact('ip'), - AllowedFilter::exact('port'), - 'ip_alias', - AllowedFilter::callback('server_id', function (Builder $builder, $value) { - if (empty($value) || is_bool($value) || !ctype_digit((string) $value)) { - return $builder->whereNull('server_id'); + 'id', 'ip', 'port', 'alias', + AllowedFilter::callback('server_id', function (Builder $query, $value) { + if ($value === '0') { + $query->whereNull('server_id'); + } else { + $query->where('server_id', '=', $value); } - - return $builder->where('server_id', $value); }), ]) - ->paginate($request->query('per_page') ?? 50); + ->allowedSorts(['id', 'ip', 'port', 'server_id']) + ->paginate($perPage); return $this->fractal->collection($allocations) - ->transformWith($this->getTransformer(AllocationTransformer::class)) + ->transformWith(AllocationTransformer::class) ->toArray(); } @@ -62,11 +67,11 @@ public function index(GetAllocationsRequest $request, Node $node): array * @throws \Pterodactyl\Exceptions\Service\Allocation\PortOutOfRangeException * @throws \Pterodactyl\Exceptions\Service\Allocation\TooManyPortsInRangeException */ - public function store(StoreAllocationRequest $request, Node $node): JsonResponse + public function store(StoreAllocationRequest $request, Node $node): Response { $this->assignmentService->handle($node, $request->validated()); - return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT); + return $this->returnNoContent(); } /** @@ -74,10 +79,10 @@ public function store(StoreAllocationRequest $request, Node $node): JsonResponse * * @throws \Pterodactyl\Exceptions\Service\Allocation\ServerUsingAllocationException */ - public function delete(DeleteAllocationRequest $request, Node $node, Allocation $allocation): JsonResponse + public function delete(DeleteAllocationRequest $request, Node $node, Allocation $allocation): Response { $this->deletionService->handle($allocation); - return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT); + return $this->returnNoContent(); } } diff --git a/app/Http/Controllers/Api/Application/Nodes/NodeConfigurationController.php b/app/Http/Controllers/Api/Application/Nodes/NodeConfigurationController.php index 2d812a89bb..4709e109a1 100644 --- a/app/Http/Controllers/Api/Application/Nodes/NodeConfigurationController.php +++ b/app/Http/Controllers/Api/Application/Nodes/NodeConfigurationController.php @@ -14,8 +14,12 @@ class NodeConfigurationController extends ApplicationApiController * to remote machines so long as an API key is provided to the machine to make the request * with, and the node is known. */ - public function __invoke(GetNodeRequest $request, Node $node): JsonResponse + public function __invoke(GetNodeRequest $request, Node $node): JsonResponse|string { + if ($request->query('format') === 'yaml') { + return $node->getYamlConfiguration(); + } + return new JsonResponse($node->getConfiguration()); } } diff --git a/app/Http/Controllers/Api/Application/Nodes/NodeController.php b/app/Http/Controllers/Api/Application/Nodes/NodeController.php index e0e3575d81..c30272d07a 100644 --- a/app/Http/Controllers/Api/Application/Nodes/NodeController.php +++ b/app/Http/Controllers/Api/Application/Nodes/NodeController.php @@ -3,12 +3,14 @@ namespace Pterodactyl\Http\Controllers\Api\Application\Nodes; use Pterodactyl\Models\Node; +use Illuminate\Http\Response; use Illuminate\Http\JsonResponse; use Spatie\QueryBuilder\QueryBuilder; use Pterodactyl\Services\Nodes\NodeUpdateService; use Pterodactyl\Services\Nodes\NodeCreationService; use Pterodactyl\Services\Nodes\NodeDeletionService; use Pterodactyl\Transformers\Api\Application\NodeTransformer; +use Pterodactyl\Exceptions\Http\QueryValueOutOfRangeHttpException; use Pterodactyl\Http\Requests\Api\Application\Nodes\GetNodeRequest; use Pterodactyl\Http\Requests\Api\Application\Nodes\GetNodesRequest; use Pterodactyl\Http\Requests\Api\Application\Nodes\StoreNodeRequest; @@ -34,13 +36,18 @@ public function __construct( */ public function index(GetNodesRequest $request): array { + $perPage = (int) $request->query('per_page', '10'); + if ($perPage < 1 || $perPage > 100) { + throw new QueryValueOutOfRangeHttpException('per_page', 1, 100); + } + $nodes = QueryBuilder::for(Node::query()) - ->allowedFilters(['uuid', 'name', 'fqdn', 'daemon_token_id']) - ->allowedSorts(['id', 'uuid', 'memory', 'disk']) - ->paginate($request->query('per_page') ?? 50); + ->allowedFilters(['id', 'uuid', 'name', 'fqdn', 'daemon_token_id']) + ->allowedSorts(['id', 'uuid', 'name', 'location_id', 'fqdn', 'memory', 'disk']) + ->paginate($perPage); return $this->fractal->collection($nodes) - ->transformWith($this->getTransformer(NodeTransformer::class)) + ->transformWith(NodeTransformer::class) ->toArray(); } @@ -50,7 +57,7 @@ public function index(GetNodesRequest $request): array public function view(GetNodeRequest $request, Node $node): array { return $this->fractal->item($node) - ->transformWith($this->getTransformer(NodeTransformer::class)) + ->transformWith(NodeTransformer::class) ->toArray(); } @@ -65,12 +72,7 @@ public function store(StoreNodeRequest $request): JsonResponse $node = $this->creationService->handle($request->validated()); return $this->fractal->item($node) - ->transformWith($this->getTransformer(NodeTransformer::class)) - ->addMeta([ - 'resource' => route('api.application.nodes.view', [ - 'node' => $node->id, - ]), - ]) + ->transformWith(NodeTransformer::class) ->respond(201); } @@ -84,11 +86,10 @@ public function update(UpdateNodeRequest $request, Node $node): array $node = $this->updateService->handle( $node, $request->validated(), - $request->input('reset_secret') === true ); return $this->fractal->item($node) - ->transformWith($this->getTransformer(NodeTransformer::class)) + ->transformWith(NodeTransformer::class) ->toArray(); } @@ -98,10 +99,10 @@ public function update(UpdateNodeRequest $request, Node $node): array * * @throws \Pterodactyl\Exceptions\Service\HasActiveServersException */ - public function delete(DeleteNodeRequest $request, Node $node): JsonResponse + public function delete(DeleteNodeRequest $request, Node $node): Response { $this->deletionService->handle($node); - return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT); + return $this->returnNoContent(); } } diff --git a/app/Http/Controllers/Api/Application/Nodes/NodeDeploymentController.php b/app/Http/Controllers/Api/Application/Nodes/NodeDeploymentController.php index 4b49fa9c6a..fa09e9707b 100644 --- a/app/Http/Controllers/Api/Application/Nodes/NodeDeploymentController.php +++ b/app/Http/Controllers/Api/Application/Nodes/NodeDeploymentController.php @@ -30,10 +30,10 @@ public function __invoke(GetDeployableNodesRequest $request): array $nodes = $this->viableNodesService->setLocations($data['location_ids'] ?? []) ->setMemory($data['memory']) ->setDisk($data['disk']) - ->handle($request->query('per_page'), $request->query('page')); + ->handle($request->query('per_page'), $request->query('page')); // @phpstan-ignore-line return $this->fractal->collection($nodes) - ->transformWith($this->getTransformer(NodeTransformer::class)) + ->transformWith(NodeTransformer::class) ->toArray(); } } diff --git a/app/Http/Controllers/Api/Application/Nodes/NodeInformationController.php b/app/Http/Controllers/Api/Application/Nodes/NodeInformationController.php new file mode 100644 index 0000000000..ff099e15b3 --- /dev/null +++ b/app/Http/Controllers/Api/Application/Nodes/NodeInformationController.php @@ -0,0 +1,47 @@ +cache + ->tags(['nodes']) + ->remember($node->uuid, Carbon::now()->addSeconds(30), function () use ($node) { + return $this->repository->setNode($node)->getSystemInformation(); + }); + + return new JsonResponse([ + 'version' => $data['version'] ?? null, + 'system' => [ + 'type' => Str::title($data['os'] ?? 'Unknown'), + 'arch' => $data['architecture'] ?? null, + 'release' => $data['kernel_version'] ?? null, + 'cpus' => $data['cpu_count'] ?? null, + ], + ]); + } +} diff --git a/app/Http/Controllers/Api/Application/Roles/RoleController.php b/app/Http/Controllers/Api/Application/Roles/RoleController.php new file mode 100644 index 0000000000..21ec404aac --- /dev/null +++ b/app/Http/Controllers/Api/Application/Roles/RoleController.php @@ -0,0 +1,96 @@ +query('per_page', '10'); + if ($perPage < 1 || $perPage > 100) { + throw new QueryValueOutOfRangeHttpException('per_page', 1, 100); + } + + $roles = QueryBuilder::for(AdminRole::query()) + ->allowedFilters(['id', 'name']) + ->allowedSorts(['id', 'name']) + ->paginate($perPage); + + return $this->fractal->collection($roles) + ->transformWith(AdminRoleTransformer::class) + ->toArray(); + } + + /** + * Returns a single role. + */ + public function view(GetRoleRequest $request, AdminRole $role): array + { + return $this->fractal->item($role) + ->transformWith(AdminRoleTransformer::class) + ->toArray(); + } + + /** + * Creates a new role. + */ + public function store(StoreRoleRequest $request): JsonResponse + { + $data = array_merge($request->validated(), [ + 'sort_id' => 99, + ]); + $role = AdminRole::query()->create($data); + + return $this->fractal->item($role) + ->transformWith(AdminRoleTransformer::class) + ->respond(JsonResponse::HTTP_CREATED); + } + + /** + * Updates a role. + */ + public function update(UpdateRoleRequest $request, AdminRole $role): array + { + $role->update($request->validated()); + + return $this->fractal->item($role) + ->transformWith(AdminRoleTransformer::class) + ->toArray(); + } + + /** + * Deletes a role. + * + * @throws \Exception + */ + public function delete(DeleteRoleRequest $request, AdminRole $role): Response + { + $role->delete(); + + return $this->returnNoContent(); + } +} diff --git a/app/Http/Controllers/Api/Application/Servers/DatabaseController.php b/app/Http/Controllers/Api/Application/Servers/DatabaseController.php index 1717c5dd88..21f0da45fd 100644 --- a/app/Http/Controllers/Api/Application/Servers/DatabaseController.php +++ b/app/Http/Controllers/Api/Application/Servers/DatabaseController.php @@ -34,7 +34,7 @@ public function __construct( public function index(GetServerDatabasesRequest $request, Server $server): array { return $this->fractal->collection($server->databases) - ->transformWith($this->getTransformer(ServerDatabaseTransformer::class)) + ->transformWith(ServerDatabaseTransformer::class) ->toArray(); } @@ -44,7 +44,7 @@ public function index(GetServerDatabasesRequest $request, Server $server): array public function view(GetServerDatabaseRequest $request, Server $server, Database $database): array { return $this->fractal->item($database) - ->transformWith($this->getTransformer(ServerDatabaseTransformer::class)) + ->transformWith(ServerDatabaseTransformer::class) ->toArray(); } @@ -53,11 +53,11 @@ public function view(GetServerDatabaseRequest $request, Server $server, Database * * @throws \Throwable */ - public function resetPassword(ServerDatabaseWriteRequest $request, Server $server, Database $database): JsonResponse + public function resetPassword(ServerDatabaseWriteRequest $request, Server $server, Database $database): Response { $this->databasePasswordService->handle($database); - return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT); + return $this->returnNoContent(); } /** @@ -72,23 +72,19 @@ public function store(StoreServerDatabaseRequest $request, Server $server): Json ])); return $this->fractal->item($database) - ->transformWith($this->getTransformer(ServerDatabaseTransformer::class)) - ->addMeta([ - 'resource' => route('api.application.servers.databases.view', [ - 'server' => $server->id, - 'database' => $database->id, - ]), - ]) + ->transformWith(ServerDatabaseTransformer::class) ->respond(Response::HTTP_CREATED); } /** * Handle a request to delete a specific server database from the Panel. + * + * @throws \Exception */ - public function delete(ServerDatabaseWriteRequest $request, Server $server, Database $database): Response + public function delete(ServerDatabaseWriteRequest $request, Database $database): Response { $this->databaseManagementService->delete($database); - return response('', 204); + return $this->returnNoContent(); } } diff --git a/app/Http/Controllers/Api/Application/Servers/ExternalServerController.php b/app/Http/Controllers/Api/Application/Servers/ExternalServerController.php index 869472f724..3f6a3624c0 100644 --- a/app/Http/Controllers/Api/Application/Servers/ExternalServerController.php +++ b/app/Http/Controllers/Api/Application/Servers/ExternalServerController.php @@ -17,7 +17,7 @@ public function index(GetExternalServerRequest $request, string $external_id): a $server = Server::query()->where('external_id', $external_id)->firstOrFail(); return $this->fractal->item($server) - ->transformWith($this->getTransformer(ServerTransformer::class)) + ->transformWith(ServerTransformer::class) ->toArray(); } } diff --git a/app/Http/Controllers/Api/Application/Servers/ServerController.php b/app/Http/Controllers/Api/Application/Servers/ServerController.php index 2eb69162dd..ef633d46b4 100644 --- a/app/Http/Controllers/Api/Application/Servers/ServerController.php +++ b/app/Http/Controllers/Api/Application/Servers/ServerController.php @@ -8,12 +8,16 @@ use Spatie\QueryBuilder\QueryBuilder; use Pterodactyl\Services\Servers\ServerCreationService; use Pterodactyl\Services\Servers\ServerDeletionService; +use Pterodactyl\Services\Servers\BuildModificationService; +use Pterodactyl\Services\Servers\DetailsModificationService; use Pterodactyl\Transformers\Api\Application\ServerTransformer; +use Pterodactyl\Exceptions\Http\QueryValueOutOfRangeHttpException; use Pterodactyl\Http\Requests\Api\Application\Servers\GetServerRequest; use Pterodactyl\Http\Requests\Api\Application\Servers\GetServersRequest; use Pterodactyl\Http\Requests\Api\Application\Servers\ServerWriteRequest; use Pterodactyl\Http\Requests\Api\Application\Servers\StoreServerRequest; use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController; +use Pterodactyl\Http\Requests\Api\Application\Servers\UpdateServerRequest; class ServerController extends ApplicationApiController { @@ -21,6 +25,8 @@ class ServerController extends ApplicationApiController * ServerController constructor. */ public function __construct( + private BuildModificationService $buildModificationService, + private DetailsModificationService $detailsModificationService, private ServerCreationService $creationService, private ServerDeletionService $deletionService ) { @@ -32,13 +38,18 @@ public function __construct( */ public function index(GetServersRequest $request): array { + $perPage = (int) $request->query('per_page', '10'); + if ($perPage < 1 || $perPage > 100) { + throw new QueryValueOutOfRangeHttpException('per_page', 1, 100); + } + $servers = QueryBuilder::for(Server::query()) - ->allowedFilters(['uuid', 'uuidShort', 'name', 'description', 'image', 'external_id']) - ->allowedSorts(['id', 'uuid']) - ->paginate($request->query('per_page') ?? 50); + ->allowedFilters(['id', 'uuid', 'uuidShort', 'name', 'owner_id', 'node_id', 'external_id']) + ->allowedSorts(['id', 'uuid', 'uuidShort', 'name', 'owner_id', 'node_id', 'status']) + ->paginate($perPage); return $this->fractal->collection($servers) - ->transformWith($this->getTransformer(ServerTransformer::class)) + ->transformWith(ServerTransformer::class) ->toArray(); } @@ -48,18 +59,17 @@ public function index(GetServersRequest $request): array * @throws \Throwable * @throws \Illuminate\Validation\ValidationException * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException * @throws \Pterodactyl\Exceptions\Service\Deployment\NoViableAllocationException * @throws \Pterodactyl\Exceptions\Service\Deployment\NoViableNodeException */ public function store(StoreServerRequest $request): JsonResponse { - $server = $this->creationService->handle($request->validated(), $request->getDeploymentObject()); + $server = $this->creationService->handle($request->validated()); return $this->fractal->item($server) - ->transformWith($this->getTransformer(ServerTransformer::class)) - ->respond(201); + ->transformWith(ServerTransformer::class) + ->respond(Response::HTTP_CREATED); } /** @@ -68,7 +78,7 @@ public function store(StoreServerRequest $request): JsonResponse public function view(GetServerRequest $request, Server $server): array { return $this->fractal->item($server) - ->transformWith($this->getTransformer(ServerTransformer::class)) + ->transformWith(ServerTransformer::class) ->toArray(); } @@ -76,6 +86,7 @@ public function view(GetServerRequest $request, Server $server): array * Deletes a server. * * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Throwable */ public function delete(ServerWriteRequest $request, Server $server, string $force = ''): Response { @@ -83,4 +94,24 @@ public function delete(ServerWriteRequest $request, Server $server, string $forc return $this->returnNoContent(); } + + /** + * Update a server. + * + * @throws \Throwable + * @throws \Illuminate\Validation\ValidationException + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Pterodactyl\Exceptions\Service\Deployment\NoViableAllocationException + * @throws \Pterodactyl\Exceptions\Service\Deployment\NoViableNodeException + */ + public function update(UpdateServerRequest $request, Server $server): array + { + $server = $this->buildModificationService->handle($server, $request->validated()); + $server = $this->detailsModificationService->returnUpdatedModel()->handle($server, $request->validated()); + + return $this->fractal->item($server) + ->transformWith(ServerTransformer::class) + ->toArray(); + } } diff --git a/app/Http/Controllers/Api/Application/Servers/ServerDetailsController.php b/app/Http/Controllers/Api/Application/Servers/ServerDetailsController.php index ae5f5438ca..3201c7b7dd 100644 --- a/app/Http/Controllers/Api/Application/Servers/ServerDetailsController.php +++ b/app/Http/Controllers/Api/Application/Servers/ServerDetailsController.php @@ -25,35 +25,31 @@ public function __construct( /** * Update the details for a specific server. * - * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Throwable */ public function details(UpdateServerDetailsRequest $request, Server $server): array { - $updated = $this->detailsModificationService->returnUpdatedModel()->handle( + $server = $this->detailsModificationService->returnUpdatedModel()->handle( $server, $request->validated() ); - return $this->fractal->item($updated) - ->transformWith($this->getTransformer(ServerTransformer::class)) + return $this->fractal->item($server) + ->transformWith(ServerTransformer::class) ->toArray(); } /** * Update the build details for a specific server. * - * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Throwable */ public function build(UpdateServerBuildConfigurationRequest $request, Server $server): array { $server = $this->buildModificationService->handle($server, $request->validated()); return $this->fractal->item($server) - ->transformWith($this->getTransformer(ServerTransformer::class)) + ->transformWith(ServerTransformer::class) ->toArray(); } } diff --git a/app/Http/Controllers/Api/Application/Servers/ServerManagementController.php b/app/Http/Controllers/Api/Application/Servers/ServerManagementController.php index d4dcaa24cb..8e20d098ba 100644 --- a/app/Http/Controllers/Api/Application/Servers/ServerManagementController.php +++ b/app/Http/Controllers/Api/Application/Servers/ServerManagementController.php @@ -12,7 +12,7 @@ class ServerManagementController extends ApplicationApiController { /** - * ServerManagementController constructor. + * SuspensionController constructor. */ public function __construct( private ReinstallServerService $reinstallServerService, @@ -48,9 +48,7 @@ public function unsuspend(ServerWriteRequest $request, Server $server): Response /** * Mark a server as needing to be reinstalled. * - * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Throwable */ public function reinstall(ServerWriteRequest $request, Server $server): Response { diff --git a/app/Http/Controllers/Api/Application/Servers/StartupController.php b/app/Http/Controllers/Api/Application/Servers/StartupController.php index 6c2da078a4..ea6c28ff0e 100644 --- a/app/Http/Controllers/Api/Application/Servers/StartupController.php +++ b/app/Http/Controllers/Api/Application/Servers/StartupController.php @@ -22,10 +22,7 @@ public function __construct(private StartupModificationService $modificationServ /** * Update the startup and environment settings for a specific server. * - * @throws \Illuminate\Validation\ValidationException - * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Throwable */ public function index(UpdateServerStartupRequest $request, Server $server): array { @@ -34,7 +31,7 @@ public function index(UpdateServerStartupRequest $request, Server $server): arra ->handle($server, $request->validated()); return $this->fractal->item($server) - ->transformWith($this->getTransformer(ServerTransformer::class)) + ->transformWith(ServerTransformer::class) ->toArray(); } } diff --git a/app/Http/Controllers/Api/Application/Users/ExternalUserController.php b/app/Http/Controllers/Api/Application/Users/ExternalUserController.php index 2a8f4f07e7..380c0cc3b4 100644 --- a/app/Http/Controllers/Api/Application/Users/ExternalUserController.php +++ b/app/Http/Controllers/Api/Application/Users/ExternalUserController.php @@ -17,7 +17,7 @@ public function index(GetExternalUserRequest $request, string $external_id): arr $user = User::query()->where('external_id', $external_id)->firstOrFail(); return $this->fractal->item($user) - ->transformWith($this->getTransformer(UserTransformer::class)) + ->transformWith(UserTransformer::class) ->toArray(); } } diff --git a/app/Http/Controllers/Api/Application/Users/UserController.php b/app/Http/Controllers/Api/Application/Users/UserController.php index 0ddae27b1e..48fd58133e 100644 --- a/app/Http/Controllers/Api/Application/Users/UserController.php +++ b/app/Http/Controllers/Api/Application/Users/UserController.php @@ -2,13 +2,19 @@ namespace Pterodactyl\Http\Controllers\Api\Application\Users; +use Illuminate\Support\Arr; use Pterodactyl\Models\User; +use Illuminate\Http\Response; use Illuminate\Http\JsonResponse; use Spatie\QueryBuilder\QueryBuilder; +use Spatie\QueryBuilder\AllowedFilter; +use Illuminate\Database\Eloquent\Builder; use Pterodactyl\Services\Users\UserUpdateService; use Pterodactyl\Services\Users\UserCreationService; use Pterodactyl\Services\Users\UserDeletionService; use Pterodactyl\Transformers\Api\Application\UserTransformer; +use Pterodactyl\Exceptions\Http\QueryValueOutOfRangeHttpException; +use Pterodactyl\Http\Requests\Api\Application\Users\GetUserRequest; use Pterodactyl\Http\Requests\Api\Application\Users\GetUsersRequest; use Pterodactyl\Http\Requests\Api\Application\Users\StoreUserRequest; use Pterodactyl\Http\Requests\Api\Application\Users\DeleteUserRequest; @@ -35,24 +41,48 @@ public function __construct( */ public function index(GetUsersRequest $request): array { + $perPage = (int) $request->query('per_page', '10'); + if ($perPage < 1 || $perPage > 100) { + throw new QueryValueOutOfRangeHttpException('per_page', 1, 100); + } + $users = QueryBuilder::for(User::query()) - ->allowedFilters(['email', 'uuid', 'username', 'external_id']) - ->allowedSorts(['id', 'uuid']) - ->paginate($request->query('per_page') ?? 50); + ->allowedFilters([ + AllowedFilter::exact('id'), + AllowedFilter::exact('uuid'), + AllowedFilter::exact('external_id'), + 'username', + 'email', + AllowedFilter::callback('*', function (Builder $builder, $value) { + foreach (Arr::wrap($value) as $datum) { + $datum = '%' . $datum . '%'; + $builder->where(function (Builder $builder) use ($datum) { + $builder->where('uuid', 'LIKE', $datum) + ->orWhere('username', 'LIKE', $datum) + ->orWhere('email', 'LIKE', $datum) + ->orWhere('external_id', 'LIKE', $datum); + }); + } + }), + ]) + ->allowedSorts(['id', 'uuid', 'username', 'email', 'admin_role_id']) + ->paginate($perPage); return $this->fractal->collection($users) - ->transformWith($this->getTransformer(UserTransformer::class)) + ->transformWith(UserTransformer::class) ->toArray(); } /** * Handle a request to view a single user. Includes any relations that * were defined in the request. + * + * @throws \Illuminate\Contracts\Container\BindingResolutionException */ - public function view(GetUsersRequest $request, User $user): array + public function view(GetUserRequest $request, User $user): array { return $this->fractal->item($user) - ->transformWith($this->getTransformer(UserTransformer::class)) + ->transformWith(UserTransformer::class) ->toArray(); } @@ -64,22 +94,20 @@ public function view(GetUsersRequest $request, User $user): array * Revocation errors are returned under the 'revocation_errors' key in the response * meta. If there are no errors this is an empty array. * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function update(UpdateUserRequest $request, User $user): array { $this->updateService->setUserLevel(User::USER_LEVEL_ADMIN); $user = $this->updateService->handle($user, $request->validated()); - $response = $this->fractal->item($user) - ->transformWith($this->getTransformer(UserTransformer::class)); - - return $response->toArray(); + return $this->fractal->item($user) + ->transformWith(UserTransformer::class) + ->toArray(); } /** - * Store a new user on the system. Returns the created user and an HTTP/201 + * Store a new user on the system. Returns the created user and a HTTP/201 * header on successful creation. * * @throws \Exception @@ -90,12 +118,7 @@ public function store(StoreUserRequest $request): JsonResponse $user = $this->creationService->handle($request->validated()); return $this->fractal->item($user) - ->transformWith($this->getTransformer(UserTransformer::class)) - ->addMeta([ - 'resource' => route('api.application.users.view', [ - 'user' => $user->id, - ]), - ]) + ->transformWith(UserTransformer::class) ->respond(201); } @@ -105,10 +128,10 @@ public function store(StoreUserRequest $request): JsonResponse * * @throws \Pterodactyl\Exceptions\DisplayException */ - public function delete(DeleteUserRequest $request, User $user): JsonResponse + public function delete(DeleteUserRequest $request, User $user): Response { $this->deletionService->handle($user); - return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT); + return $this->returnNoContent(); } } diff --git a/app/Http/Controllers/Api/Application/VersionController.php b/app/Http/Controllers/Api/Application/VersionController.php new file mode 100644 index 0000000000..9bd2203ed8 --- /dev/null +++ b/app/Http/Controllers/Api/Application/VersionController.php @@ -0,0 +1,25 @@ +softwareVersionService->getVersionData()); + } +} diff --git a/app/Http/Controllers/Api/Client/AccountController.php b/app/Http/Controllers/Api/Client/AccountController.php index cbe8b03156..468a751c0c 100644 --- a/app/Http/Controllers/Api/Client/AccountController.php +++ b/app/Http/Controllers/Api/Client/AccountController.php @@ -25,7 +25,7 @@ public function __construct(private AuthManager $manager, private UserUpdateServ public function index(Request $request): array { return $this->fractal->item($request->user()) - ->transformWith($this->getTransformer(AccountTransformer::class)) + ->transformWith(AccountTransformer::class) ->toArray(); } diff --git a/app/Http/Controllers/Api/Client/ActivityLogController.php b/app/Http/Controllers/Api/Client/ActivityLogController.php index dbbd06c06b..f6e9033e26 100644 --- a/app/Http/Controllers/Api/Client/ActivityLogController.php +++ b/app/Http/Controllers/Api/Client/ActivityLogController.php @@ -24,7 +24,7 @@ public function __invoke(ClientApiRequest $request): array ->appends($request->query()); return $this->fractal->collection($activity) - ->transformWith($this->getTransformer(ActivityLogTransformer::class)) + ->transformWith(ActivityLogTransformer::class) ->toArray(); } } diff --git a/app/Http/Controllers/Api/Client/ApiKeyController.php b/app/Http/Controllers/Api/Client/ApiKeyController.php index ac00a4d8f2..e60ac59cd2 100644 --- a/app/Http/Controllers/Api/Client/ApiKeyController.php +++ b/app/Http/Controllers/Api/Client/ApiKeyController.php @@ -18,7 +18,7 @@ class ApiKeyController extends ClientApiController public function index(ClientApiRequest $request): array { return $this->fractal->collection($request->user()->apiKeys) - ->transformWith($this->getTransformer(ApiKeyTransformer::class)) + ->transformWith(ApiKeyTransformer::class) ->toArray(); } @@ -44,7 +44,7 @@ public function store(StoreApiKeyRequest $request): array ->log(); return $this->fractal->item($token->accessToken) - ->transformWith($this->getTransformer(ApiKeyTransformer::class)) + ->transformWith(ApiKeyTransformer::class) ->addMeta(['secret_token' => $token->plainTextToken]) ->toArray(); } diff --git a/app/Http/Controllers/Api/Client/ClientApiController.php b/app/Http/Controllers/Api/Client/ClientApiController.php index a468d762a9..380cbf5480 100644 --- a/app/Http/Controllers/Api/Client/ClientApiController.php +++ b/app/Http/Controllers/Api/Client/ClientApiController.php @@ -3,7 +3,7 @@ namespace Pterodactyl\Http\Controllers\Api\Client; use Webmozart\Assert\Assert; -use Pterodactyl\Transformers\Api\Client\BaseClientTransformer; +use Pterodactyl\Transformers\Api\Transformer; use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController; abstract class ClientApiController extends ApplicationApiController @@ -11,7 +11,7 @@ abstract class ClientApiController extends ApplicationApiController /** * Returns only the includes which are valid for the given transformer. */ - protected function getIncludesForTransformer(BaseClientTransformer $transformer, array $merge = []): array + protected function getIncludesForTransformer(Transformer $transformer, array $merge = []): array { $filtered = array_filter($this->parseIncludes(), function ($datum) use ($transformer) { return in_array($datum, $transformer->getAvailableIncludes()); @@ -35,22 +35,4 @@ protected function parseIncludes(): array return trim($item); }, explode(',', $includes)); } - - /** - * Return an instance of an application transformer. - * - * @template T of \Pterodactyl\Transformers\Api\Client\BaseClientTransformer - * - * @param class-string $abstract - * - * @return T - * - * @noinspection PhpDocSignatureInspection - */ - public function getTransformer(string $abstract) - { - Assert::subclassOf($abstract, BaseClientTransformer::class); - - return $abstract::fromRequest($this->request); - } } diff --git a/app/Http/Controllers/Api/Client/ClientController.php b/app/Http/Controllers/Api/Client/ClientController.php index dcdb5964bc..d4d5ae6c5f 100644 --- a/app/Http/Controllers/Api/Client/ClientController.php +++ b/app/Http/Controllers/Api/Client/ClientController.php @@ -27,7 +27,7 @@ public function __construct() public function index(GetServersRequest $request): array { $user = $request->user(); - $transformer = $this->getTransformer(ServerTransformer::class); + $transformer = new ServerTransformer(); // Start the query builder and ensure we eager load any requested relationships from the request. $builder = QueryBuilder::for( diff --git a/app/Http/Controllers/Api/Client/SSHKeyController.php b/app/Http/Controllers/Api/Client/SSHKeyController.php index aa0f0c6860..794c44cf05 100644 --- a/app/Http/Controllers/Api/Client/SSHKeyController.php +++ b/app/Http/Controllers/Api/Client/SSHKeyController.php @@ -17,7 +17,7 @@ class SSHKeyController extends ClientApiController public function index(ClientApiRequest $request): array { return $this->fractal->collection($request->user()->sshKeys) - ->transformWith($this->getTransformer(UserSSHKeyTransformer::class)) + ->transformWith(UserSSHKeyTransformer::class) ->toArray(); } @@ -38,7 +38,7 @@ public function store(StoreSSHKeyRequest $request): array ->log(); return $this->fractal->item($model) - ->transformWith($this->getTransformer(UserSSHKeyTransformer::class)) + ->transformWith(UserSSHKeyTransformer::class) ->toArray(); } diff --git a/app/Http/Controllers/Api/Client/Servers/ActivityLogController.php b/app/Http/Controllers/Api/Client/Servers/ActivityLogController.php index f569c41220..e3e083d48e 100644 --- a/app/Http/Controllers/Api/Client/Servers/ActivityLogController.php +++ b/app/Http/Controllers/Api/Client/Servers/ActivityLogController.php @@ -48,7 +48,7 @@ public function __invoke(ClientApiRequest $request, Server $server): array ->appends($request->query()); return $this->fractal->collection($activity) - ->transformWith($this->getTransformer(ActivityLogTransformer::class)) + ->transformWith(ActivityLogTransformer::class) ->toArray(); } } diff --git a/app/Http/Controllers/Api/Client/Servers/BackupController.php b/app/Http/Controllers/Api/Client/Servers/BackupController.php index 7a35341c10..13c9ac7eac 100644 --- a/app/Http/Controllers/Api/Client/Servers/BackupController.php +++ b/app/Http/Controllers/Api/Client/Servers/BackupController.php @@ -49,7 +49,7 @@ public function index(Request $request, Server $server): array $limit = min($request->query('per_page') ?? 20, 50); return $this->fractal->collection($server->backups()->paginate($limit)) - ->transformWith($this->getTransformer(BackupTransformer::class)) + ->transformWith(BackupTransformer::class) ->addMeta([ 'backup_count' => $this->repository->getNonFailedBackups($server)->count(), ]) @@ -84,7 +84,7 @@ public function store(StoreBackupRequest $request, Server $server): array ->log(); return $this->fractal->item($backup) - ->transformWith($this->getTransformer(BackupTransformer::class)) + ->transformWith(BackupTransformer::class) ->toArray(); } @@ -107,7 +107,7 @@ public function toggleLock(Request $request, Server $server, Backup $backup): ar Activity::event($action)->subject($backup)->property('name', $backup->name)->log(); return $this->fractal->item($backup) - ->transformWith($this->getTransformer(BackupTransformer::class)) + ->transformWith(BackupTransformer::class) ->toArray(); } @@ -123,7 +123,7 @@ public function view(Request $request, Server $server, Backup $backup): array } return $this->fractal->item($backup) - ->transformWith($this->getTransformer(BackupTransformer::class)) + ->transformWith(BackupTransformer::class) ->toArray(); } diff --git a/app/Http/Controllers/Api/Client/Servers/DatabaseController.php b/app/Http/Controllers/Api/Client/Servers/DatabaseController.php index 728c67e913..5173cab2c6 100644 --- a/app/Http/Controllers/Api/Client/Servers/DatabaseController.php +++ b/app/Http/Controllers/Api/Client/Servers/DatabaseController.php @@ -35,7 +35,7 @@ public function __construct( public function index(GetDatabasesRequest $request, Server $server): array { return $this->fractal->collection($server->databases) - ->transformWith($this->getTransformer(DatabaseTransformer::class)) + ->transformWith(DatabaseTransformer::class) ->toArray(); } @@ -57,7 +57,7 @@ public function store(StoreDatabaseRequest $request, Server $server): array return $this->fractal->item($database) ->parseIncludes(['password']) - ->transformWith($this->getTransformer(DatabaseTransformer::class)) + ->transformWith(DatabaseTransformer::class) ->toArray(); } @@ -79,7 +79,7 @@ public function rotatePassword(RotatePasswordRequest $request, Server $server, D return $this->fractal->item($database) ->parseIncludes(['password']) - ->transformWith($this->getTransformer(DatabaseTransformer::class)) + ->transformWith(DatabaseTransformer::class) ->toArray(); } diff --git a/app/Http/Controllers/Api/Client/Servers/FileController.php b/app/Http/Controllers/Api/Client/Servers/FileController.php index 23e6718a50..eaee7f8cc9 100644 --- a/app/Http/Controllers/Api/Client/Servers/FileController.php +++ b/app/Http/Controllers/Api/Client/Servers/FileController.php @@ -47,7 +47,7 @@ public function directory(ListFilesRequest $request, Server $server): array ->getDirectory($request->get('directory') ?? '/'); return $this->fractal->collection($contents) - ->transformWith($this->getTransformer(FileObjectTransformer::class)) + ->transformWith(FileObjectTransformer::class) ->toArray(); } @@ -183,7 +183,7 @@ public function compress(CompressFilesRequest $request, Server $server): array ->log(); return $this->fractal->item($file) - ->transformWith($this->getTransformer(FileObjectTransformer::class)) + ->transformWith(FileObjectTransformer::class) ->toArray(); } diff --git a/app/Http/Controllers/Api/Client/Servers/NetworkAllocationController.php b/app/Http/Controllers/Api/Client/Servers/NetworkAllocationController.php index 4e3c5f9bbd..2fd97101bf 100644 --- a/app/Http/Controllers/Api/Client/Servers/NetworkAllocationController.php +++ b/app/Http/Controllers/Api/Client/Servers/NetworkAllocationController.php @@ -36,7 +36,7 @@ public function __construct( public function index(GetNetworkRequest $request, Server $server): array { return $this->fractal->collection($server->allocations) - ->transformWith($this->getTransformer(AllocationTransformer::class)) + ->transformWith(AllocationTransformer::class) ->toArray(); } @@ -60,7 +60,7 @@ public function update(UpdateAllocationRequest $request, Server $server, Allocat } return $this->fractal->item($allocation) - ->transformWith($this->getTransformer(AllocationTransformer::class)) + ->transformWith(AllocationTransformer::class) ->toArray(); } @@ -80,7 +80,7 @@ public function setPrimary(SetPrimaryAllocationRequest $request, Server $server, ->log(); return $this->fractal->item($allocation) - ->transformWith($this->getTransformer(AllocationTransformer::class)) + ->transformWith(AllocationTransformer::class) ->toArray(); } @@ -104,7 +104,7 @@ public function store(NewAllocationRequest $request, Server $server): array ->log(); return $this->fractal->item($allocation) - ->transformWith($this->getTransformer(AllocationTransformer::class)) + ->transformWith(AllocationTransformer::class) ->toArray(); } diff --git a/app/Http/Controllers/Api/Client/Servers/ResourceUtilizationController.php b/app/Http/Controllers/Api/Client/Servers/ResourceUtilizationController.php index dcaf481156..daf3219a1f 100644 --- a/app/Http/Controllers/Api/Client/Servers/ResourceUtilizationController.php +++ b/app/Http/Controllers/Api/Client/Servers/ResourceUtilizationController.php @@ -35,7 +35,7 @@ public function __invoke(GetServerRequest $request, Server $server): array }); return $this->fractal->item($stats) - ->transformWith($this->getTransformer(StatsTransformer::class)) + ->transformWith(StatsTransformer::class) ->toArray(); } } diff --git a/app/Http/Controllers/Api/Client/Servers/ScheduleController.php b/app/Http/Controllers/Api/Client/Servers/ScheduleController.php index b955fb16bf..20fc4e6586 100644 --- a/app/Http/Controllers/Api/Client/Servers/ScheduleController.php +++ b/app/Http/Controllers/Api/Client/Servers/ScheduleController.php @@ -40,7 +40,7 @@ public function index(ViewScheduleRequest $request, Server $server): array $schedules = $server->schedules->loadMissing('tasks'); return $this->fractal->collection($schedules) - ->transformWith($this->getTransformer(ScheduleTransformer::class)) + ->transformWith(ScheduleTransformer::class) ->toArray(); } @@ -72,7 +72,7 @@ public function store(StoreScheduleRequest $request, Server $server): array ->log(); return $this->fractal->item($model) - ->transformWith($this->getTransformer(ScheduleTransformer::class)) + ->transformWith(ScheduleTransformer::class) ->toArray(); } @@ -88,7 +88,7 @@ public function view(ViewScheduleRequest $request, Server $server, Schedule $sch $schedule->loadMissing('tasks'); return $this->fractal->item($schedule) - ->transformWith($this->getTransformer(ScheduleTransformer::class)) + ->transformWith(ScheduleTransformer::class) ->toArray(); } @@ -131,7 +131,7 @@ public function update(UpdateScheduleRequest $request, Server $server, Schedule ->log(); return $this->fractal->item($schedule->refresh()) - ->transformWith($this->getTransformer(ScheduleTransformer::class)) + ->transformWith(ScheduleTransformer::class) ->toArray(); } diff --git a/app/Http/Controllers/Api/Client/Servers/ScheduleTaskController.php b/app/Http/Controllers/Api/Client/Servers/ScheduleTaskController.php index addca2876b..e6427753b5 100644 --- a/app/Http/Controllers/Api/Client/Servers/ScheduleTaskController.php +++ b/app/Http/Controllers/Api/Client/Servers/ScheduleTaskController.php @@ -64,7 +64,7 @@ public function store(StoreTaskRequest $request, Server $server, Schedule $sched ->log(); return $this->fractal->item($task) - ->transformWith($this->getTransformer(TaskTransformer::class)) + ->transformWith(TaskTransformer::class) ->toArray(); } @@ -97,7 +97,7 @@ public function update(StoreTaskRequest $request, Server $server, Schedule $sche ->log(); return $this->fractal->item($task->refresh()) - ->transformWith($this->getTransformer(TaskTransformer::class)) + ->transformWith(TaskTransformer::class) ->toArray(); } diff --git a/app/Http/Controllers/Api/Client/Servers/ServerController.php b/app/Http/Controllers/Api/Client/Servers/ServerController.php index 63eb9b9889..ac898e2b69 100644 --- a/app/Http/Controllers/Api/Client/Servers/ServerController.php +++ b/app/Http/Controllers/Api/Client/Servers/ServerController.php @@ -25,7 +25,7 @@ public function __construct(private GetUserPermissionsService $permissionsServic public function index(GetServerRequest $request, Server $server): array { return $this->fractal->item($server) - ->transformWith($this->getTransformer(ServerTransformer::class)) + ->transformWith(ServerTransformer::class) ->addMeta([ 'is_server_owner' => $request->user()->id === $server->owner_id, 'user_permissions' => $this->permissionsService->handle($server, $request->user()), diff --git a/app/Http/Controllers/Api/Client/Servers/StartupController.php b/app/Http/Controllers/Api/Client/Servers/StartupController.php index b674145ff2..56b632617b 100644 --- a/app/Http/Controllers/Api/Client/Servers/StartupController.php +++ b/app/Http/Controllers/Api/Client/Servers/StartupController.php @@ -34,7 +34,7 @@ public function index(GetStartupRequest $request, Server $server): array return $this->fractal->collection( $server->variables()->where('user_viewable', true)->get() ) - ->transformWith($this->getTransformer(EggVariableTransformer::class)) + ->transformWith(EggVariableTransformer::class) ->addMeta([ 'startup_command' => $startup, 'docker_images' => $server->egg->docker_images, @@ -90,7 +90,7 @@ public function update(UpdateStartupVariableRequest $request, Server $server): a } return $this->fractal->item($variable) - ->transformWith($this->getTransformer(EggVariableTransformer::class)) + ->transformWith(EggVariableTransformer::class) ->addMeta([ 'startup_command' => $startup, 'raw_startup_command' => $server->startup, diff --git a/app/Http/Controllers/Api/Client/Servers/SubuserController.php b/app/Http/Controllers/Api/Client/Servers/SubuserController.php index 2c403c691d..ac402abb97 100644 --- a/app/Http/Controllers/Api/Client/Servers/SubuserController.php +++ b/app/Http/Controllers/Api/Client/Servers/SubuserController.php @@ -38,7 +38,7 @@ public function __construct( public function index(GetSubuserRequest $request, Server $server): array { return $this->fractal->collection($server->subusers) - ->transformWith($this->getTransformer(SubuserTransformer::class)) + ->transformWith(SubuserTransformer::class) ->toArray(); } @@ -50,7 +50,7 @@ public function view(GetSubuserRequest $request): array $subuser = $request->attributes->get('subuser'); return $this->fractal->item($subuser) - ->transformWith($this->getTransformer(SubuserTransformer::class)) + ->transformWith(SubuserTransformer::class) ->toArray(); } @@ -76,7 +76,7 @@ public function store(StoreSubuserRequest $request, Server $server): array ->log(); return $this->fractal->item($response) - ->transformWith($this->getTransformer(SubuserTransformer::class)) + ->transformWith(SubuserTransformer::class) ->toArray(); } @@ -129,7 +129,7 @@ public function update(UpdateSubuserRequest $request, Server $server): array $log->reset(); return $this->fractal->item($subuser->refresh()) - ->transformWith($this->getTransformer(SubuserTransformer::class)) + ->transformWith(SubuserTransformer::class) ->toArray(); } diff --git a/app/Http/Controllers/Auth/AbstractLoginController.php b/app/Http/Controllers/Auth/AbstractLoginController.php index 74e7a9aa3a..c76a5afc22 100644 --- a/app/Http/Controllers/Auth/AbstractLoginController.php +++ b/app/Http/Controllers/Auth/AbstractLoginController.php @@ -85,7 +85,7 @@ protected function sendLoginResponse(User $user, Request $request): JsonResponse 'data' => [ 'complete' => true, 'intended' => $this->redirectPath(), - 'user' => $user->toVueObject(), + 'user' => $user->toReactObject(), ], ]); } diff --git a/app/Http/Requests/Api/ApiRequest.php b/app/Http/Requests/Api/ApiRequest.php new file mode 100644 index 0000000000..253a616912 --- /dev/null +++ b/app/Http/Requests/Api/ApiRequest.php @@ -0,0 +1,83 @@ +passesAuthorization()) { + $this->failedAuthorization(); + } + + $this->hasValidated = true; + } + + /* + * Determine if the request passes the authorization check as well + * as the exists check. + * + * @return bool + * + * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException + */ + protected function passesAuthorization() + { + // If we have already validated we do not need to call this function + // again. This is needed to work around Laravel's normal auth validation + // that occurs after validating the request params since we are doing auth + // validation in the prepareForValidation() function. + if ($this->hasValidated) { + return true; + } + + if (!parent::passesAuthorization()) { + return false; + } + + // Only let the user know that a resource does not exist if they are + // authenticated to access the endpoint. This avoids exposing that + // an item exists (or does not exist) to the user until they can prove + // that they have permission to know about it. + if ($this->attributes->get('is_missing_model', false)) { + throw new NotFoundHttpException(trans('exceptions.api.resource_not_found')); + } + + return true; + } +} diff --git a/app/Http/Requests/Api/Application/Allocations/DeleteAllocationRequest.php b/app/Http/Requests/Api/Application/Allocations/DeleteAllocationRequest.php index 6529a9a5af..d062f7648f 100644 --- a/app/Http/Requests/Api/Application/Allocations/DeleteAllocationRequest.php +++ b/app/Http/Requests/Api/Application/Allocations/DeleteAllocationRequest.php @@ -2,12 +2,8 @@ namespace Pterodactyl\Http\Requests\Api\Application\Allocations; -use Pterodactyl\Services\Acl\Api\AdminAcl; use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest; class DeleteAllocationRequest extends ApplicationApiRequest { - protected ?string $resource = AdminAcl::RESOURCE_ALLOCATIONS; - - protected int $permission = AdminAcl::WRITE; } diff --git a/app/Http/Requests/Api/Application/Allocations/GetAllocationsRequest.php b/app/Http/Requests/Api/Application/Allocations/GetAllocationsRequest.php index f03223f2d2..2f536af60c 100644 --- a/app/Http/Requests/Api/Application/Allocations/GetAllocationsRequest.php +++ b/app/Http/Requests/Api/Application/Allocations/GetAllocationsRequest.php @@ -2,12 +2,8 @@ namespace Pterodactyl\Http\Requests\Api\Application\Allocations; -use Pterodactyl\Services\Acl\Api\AdminAcl; use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest; class GetAllocationsRequest extends ApplicationApiRequest { - protected ?string $resource = AdminAcl::RESOURCE_ALLOCATIONS; - - protected int $permission = AdminAcl::READ; } diff --git a/app/Http/Requests/Api/Application/Allocations/StoreAllocationRequest.php b/app/Http/Requests/Api/Application/Allocations/StoreAllocationRequest.php index a7e0c4da2d..0474408abe 100644 --- a/app/Http/Requests/Api/Application/Allocations/StoreAllocationRequest.php +++ b/app/Http/Requests/Api/Application/Allocations/StoreAllocationRequest.php @@ -2,15 +2,11 @@ namespace Pterodactyl\Http\Requests\Api\Application\Allocations; -use Pterodactyl\Services\Acl\Api\AdminAcl; +use Illuminate\Support\Arr; use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest; class StoreAllocationRequest extends ApplicationApiRequest { - protected ?string $resource = AdminAcl::RESOURCE_ALLOCATIONS; - - protected int $permission = AdminAcl::WRITE; - public function rules(): array { return [ @@ -21,14 +17,22 @@ public function rules(): array ]; } - public function validated($key = null, $default = null): array + /** + * @param string|null $key + * @param string|array|null $default + * + * @return mixed + */ + public function validated($key = null, $default = null) { $data = parent::validated(); - return [ + $response = [ 'allocation_ip' => $data['ip'], 'allocation_ports' => $data['ports'], 'allocation_alias' => $data['alias'] ?? null, ]; + + return is_null($key) ? $response : Arr::get($response, $key, $default); } } diff --git a/app/Http/Requests/Api/Application/ApplicationApiRequest.php b/app/Http/Requests/Api/Application/ApplicationApiRequest.php index 082bc6921f..70f6cb2197 100644 --- a/app/Http/Requests/Api/Application/ApplicationApiRequest.php +++ b/app/Http/Requests/Api/Application/ApplicationApiRequest.php @@ -2,94 +2,16 @@ namespace Pterodactyl\Http\Requests\Api\Application; -use Webmozart\Assert\Assert; -use Pterodactyl\Models\ApiKey; -use Laravel\Sanctum\TransientToken; -use Illuminate\Validation\Validator; -use Illuminate\Database\Eloquent\Model; -use Pterodactyl\Services\Acl\Api\AdminAcl; -use Illuminate\Foundation\Http\FormRequest; -use Pterodactyl\Exceptions\PterodactylException; +use Pterodactyl\Http\Requests\Api\ApiRequest; -abstract class ApplicationApiRequest extends FormRequest +abstract class ApplicationApiRequest extends ApiRequest { /** - * The resource that should be checked when performing the authorization - * function for this request. - */ - protected ?string $resource; - - /** - * The permission level that a given API key should have for accessing - * the defined $resource during the request cycle. - */ - protected int $permission = AdminAcl::NONE; - - /** - * Determine if the current user is authorized to perform - * the requested action against the API. - * - * @throws PterodactylException + * This will eventually be replaced with per-request permissions checking + * on the API key and for the user. */ public function authorize(): bool { - if (is_null($this->resource)) { - throw new PterodactylException('An ACL resource must be defined on API requests.'); - } - - $token = $this->user()->currentAccessToken(); - if ($token instanceof TransientToken) { - return true; - } - - /** @var ApiKey $token */ - if ($token->key_type === ApiKey::TYPE_ACCOUNT) { - return true; - } - - return AdminAcl::check($token, $this->resource, $this->permission); - } - - /** - * Default set of rules to apply to API requests. - */ - public function rules(): array - { - return []; - } - - /** - * Helper method allowing a developer to easily hook into this logic without having - * to remember what the method name is called or where to use it. By default this is - * a no-op. - */ - public function withValidator(Validator $validator): void - { - // do nothing - } - - /** - * Returns the named route parameter and asserts that it is a real model that - * exists in the database. - * - * @template T of \Illuminate\Database\Eloquent\Model - * - * @param class-string $expect - * - * @return T - * - * @noinspection PhpDocSignatureInspection - */ - public function parameter(string $key, string $expect) - { - /** @var ApiKey $value */ - $value = $this->route()->parameter($key); - - Assert::isInstanceOf($value, $expect); - Assert::isInstanceOf($value, Model::class); - Assert::true($value->exists); - - /* @var T $value */ - return $value; + return $this->user()->root_admin; } } diff --git a/app/Http/Requests/Api/Application/Databases/DeleteDatabaseRequest.php b/app/Http/Requests/Api/Application/Databases/DeleteDatabaseRequest.php new file mode 100644 index 0000000000..cde56da1c7 --- /dev/null +++ b/app/Http/Requests/Api/Application/Databases/DeleteDatabaseRequest.php @@ -0,0 +1,9 @@ +route()->parameter('databaseHost')->id); + } +} diff --git a/app/Http/Requests/Api/Application/Eggs/DeleteEggRequest.php b/app/Http/Requests/Api/Application/Eggs/DeleteEggRequest.php new file mode 100644 index 0000000000..154f06efdf --- /dev/null +++ b/app/Http/Requests/Api/Application/Eggs/DeleteEggRequest.php @@ -0,0 +1,16 @@ +route()->parameter('egg'); + + return $egg instanceof Egg && $egg->exists; + } +} diff --git a/app/Http/Requests/Api/Application/Eggs/ExportEggRequest.php b/app/Http/Requests/Api/Application/Eggs/ExportEggRequest.php new file mode 100644 index 0000000000..63893df542 --- /dev/null +++ b/app/Http/Requests/Api/Application/Eggs/ExportEggRequest.php @@ -0,0 +1,9 @@ + 'required|bail|numeric|exists:nests,id', + 'name' => 'required|string|max:191', + 'description' => 'sometimes|string|nullable', + 'features' => 'sometimes|array', + 'docker_images' => 'required|array|min:1', + 'docker_images.*' => 'required|string', + 'file_denylist' => 'sometimes|array|nullable', + 'file_denylist.*' => 'sometimes|string', + 'config_files' => 'required|nullable|json', + 'config_startup' => 'required|nullable|json', + 'config_stop' => 'required|nullable|string|max:191', +// 'config_from' => 'sometimes|nullable|numeric|exists:eggs,id', + 'startup' => 'required|string', + 'script_container' => 'sometimes|string', + 'script_entry' => 'sometimes|string', + 'script_install' => 'sometimes|string', + ]; + } +} diff --git a/app/Http/Requests/Api/Application/Eggs/UpdateEggRequest.php b/app/Http/Requests/Api/Application/Eggs/UpdateEggRequest.php new file mode 100644 index 0000000000..090f5b51c2 --- /dev/null +++ b/app/Http/Requests/Api/Application/Eggs/UpdateEggRequest.php @@ -0,0 +1,28 @@ + 'sometimes|numeric|exists:nests,id', + 'name' => 'sometimes|string|max:191', + 'description' => 'sometimes|string|nullable', + 'features' => 'sometimes|array', + 'docker_images' => 'sometimes|array|min:1', + 'docker_images.*' => 'sometimes|string', + 'file_denylist' => 'sometimes|array|nullable', + 'file_denylist.*' => 'sometimes|string', + 'config_files' => 'sometimes|nullable|json', + 'config_startup' => 'sometimes|nullable|json', + 'config_stop' => 'sometimes|nullable|string|max:191', +// 'config_from' => 'sometimes|nullable|numeric|exists:eggs,id', + 'startup' => 'sometimes|string', + 'script_container' => 'sometimes|string', + 'script_entry' => 'sometimes|string', + 'script_install' => 'sometimes|string', + ]; + } +} diff --git a/app/Http/Requests/Api/Application/Eggs/Variables/StoreEggVariableRequest.php b/app/Http/Requests/Api/Application/Eggs/Variables/StoreEggVariableRequest.php new file mode 100644 index 0000000000..9c674a9d88 --- /dev/null +++ b/app/Http/Requests/Api/Application/Eggs/Variables/StoreEggVariableRequest.php @@ -0,0 +1,22 @@ + 'required|string|min:1|max:191', + 'description' => 'sometimes|string|nullable', + 'env_variable' => 'required|regex:/^[\w]{1,191}$/|notIn:' . EggVariable::RESERVED_ENV_NAMES, + 'default_value' => 'present', + 'user_viewable' => 'required|boolean', + 'user_editable' => 'required|boolean', + 'rules' => 'bail|required|string', + ]; + } +} diff --git a/app/Http/Requests/Api/Application/Eggs/Variables/UpdateEggVariablesRequest.php b/app/Http/Requests/Api/Application/Eggs/Variables/UpdateEggVariablesRequest.php new file mode 100644 index 0000000000..c15de2ce30 --- /dev/null +++ b/app/Http/Requests/Api/Application/Eggs/Variables/UpdateEggVariablesRequest.php @@ -0,0 +1,24 @@ + 'array', + '*.id' => 'required|integer', + '*.name' => 'sometimes|string|min:1|max:191', + '*.description' => 'sometimes|string|nullable', + '*.env_variable' => 'sometimes|regex:/^[\w]{1,191}$/|notIn:' . EggVariable::RESERVED_ENV_NAMES, + '*.default_value' => 'sometimes|present', + '*.user_viewable' => 'sometimes|boolean', + '*.user_editable' => 'sometimes|boolean', + '*.rules' => 'sometimes|string', + ]; + } +} diff --git a/app/Http/Requests/Api/Application/Locations/DeleteLocationRequest.php b/app/Http/Requests/Api/Application/Locations/DeleteLocationRequest.php index 3c41e11663..eb2cffd34c 100644 --- a/app/Http/Requests/Api/Application/Locations/DeleteLocationRequest.php +++ b/app/Http/Requests/Api/Application/Locations/DeleteLocationRequest.php @@ -2,12 +2,8 @@ namespace Pterodactyl\Http\Requests\Api\Application\Locations; -use Pterodactyl\Services\Acl\Api\AdminAcl; use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest; class DeleteLocationRequest extends ApplicationApiRequest { - protected ?string $resource = AdminAcl::RESOURCE_LOCATIONS; - - protected int $permission = AdminAcl::WRITE; } diff --git a/app/Http/Requests/Api/Application/Locations/GetLocationsRequest.php b/app/Http/Requests/Api/Application/Locations/GetLocationsRequest.php index 65157dc475..dea300b91b 100644 --- a/app/Http/Requests/Api/Application/Locations/GetLocationsRequest.php +++ b/app/Http/Requests/Api/Application/Locations/GetLocationsRequest.php @@ -2,12 +2,8 @@ namespace Pterodactyl\Http\Requests\Api\Application\Locations; -use Pterodactyl\Services\Acl\Api\AdminAcl; use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest; class GetLocationsRequest extends ApplicationApiRequest { - protected ?string $resource = AdminAcl::RESOURCE_LOCATIONS; - - protected int $permission = AdminAcl::READ; } diff --git a/app/Http/Requests/Api/Application/Locations/StoreLocationRequest.php b/app/Http/Requests/Api/Application/Locations/StoreLocationRequest.php index cf0b126299..9b403fa104 100644 --- a/app/Http/Requests/Api/Application/Locations/StoreLocationRequest.php +++ b/app/Http/Requests/Api/Application/Locations/StoreLocationRequest.php @@ -3,18 +3,10 @@ namespace Pterodactyl\Http\Requests\Api\Application\Locations; use Pterodactyl\Models\Location; -use Pterodactyl\Services\Acl\Api\AdminAcl; use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest; class StoreLocationRequest extends ApplicationApiRequest { - protected ?string $resource = AdminAcl::RESOURCE_LOCATIONS; - - protected int $permission = AdminAcl::WRITE; - - /** - * Rules to validate the request against. - */ public function rules(): array { return collect(Location::getRules())->only([ @@ -23,9 +15,6 @@ public function rules(): array ])->toArray(); } - /** - * Rename fields to be more clear in error messages. - */ public function attributes(): array { return [ diff --git a/app/Http/Requests/Api/Application/Locations/UpdateLocationRequest.php b/app/Http/Requests/Api/Application/Locations/UpdateLocationRequest.php index b7acac9781..91ece11fe9 100644 --- a/app/Http/Requests/Api/Application/Locations/UpdateLocationRequest.php +++ b/app/Http/Requests/Api/Application/Locations/UpdateLocationRequest.php @@ -6,14 +6,9 @@ class UpdateLocationRequest extends StoreLocationRequest { - /** - * Rules to validate this request against. - */ public function rules(): array { - /** @var Location $location */ - $location = $this->route()->parameter('location'); - $locationId = $location->id; + $locationId = $this->route()->parameter('location')->id; return collect(Location::getRulesForUpdate($locationId))->only([ 'short', diff --git a/app/Http/Requests/Api/Application/Mounts/DeleteMountRequest.php b/app/Http/Requests/Api/Application/Mounts/DeleteMountRequest.php new file mode 100644 index 0000000000..1325510f27 --- /dev/null +++ b/app/Http/Requests/Api/Application/Mounts/DeleteMountRequest.php @@ -0,0 +1,9 @@ + 'required|exists:eggs,id']; + } +} diff --git a/app/Http/Requests/Api/Application/Mounts/MountNodesRequest.php b/app/Http/Requests/Api/Application/Mounts/MountNodesRequest.php new file mode 100644 index 0000000000..4810591a82 --- /dev/null +++ b/app/Http/Requests/Api/Application/Mounts/MountNodesRequest.php @@ -0,0 +1,13 @@ + 'required|exists:nodes,id']; + } +} diff --git a/app/Http/Requests/Api/Application/Mounts/StoreMountRequest.php b/app/Http/Requests/Api/Application/Mounts/StoreMountRequest.php new file mode 100644 index 0000000000..ba678d186d --- /dev/null +++ b/app/Http/Requests/Api/Application/Mounts/StoreMountRequest.php @@ -0,0 +1,14 @@ +route()->parameter('mount')->id); + } +} diff --git a/app/Http/Requests/Api/Application/Nests/DeleteNestRequest.php b/app/Http/Requests/Api/Application/Nests/DeleteNestRequest.php new file mode 100644 index 0000000000..8d505c93ee --- /dev/null +++ b/app/Http/Requests/Api/Application/Nests/DeleteNestRequest.php @@ -0,0 +1,9 @@ +route()->parameter('nest')->id); + } +} diff --git a/app/Http/Requests/Api/Application/Nodes/DeleteNodeRequest.php b/app/Http/Requests/Api/Application/Nodes/DeleteNodeRequest.php index 01f503f3f0..4bd5159d04 100644 --- a/app/Http/Requests/Api/Application/Nodes/DeleteNodeRequest.php +++ b/app/Http/Requests/Api/Application/Nodes/DeleteNodeRequest.php @@ -2,12 +2,8 @@ namespace Pterodactyl\Http\Requests\Api\Application\Nodes; -use Pterodactyl\Services\Acl\Api\AdminAcl; use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest; class DeleteNodeRequest extends ApplicationApiRequest { - protected ?string $resource = AdminAcl::RESOURCE_NODES; - - protected int $permission = AdminAcl::WRITE; } diff --git a/app/Http/Requests/Api/Application/Nodes/GetNodesRequest.php b/app/Http/Requests/Api/Application/Nodes/GetNodesRequest.php index 5c8524b945..ac6191ea5e 100644 --- a/app/Http/Requests/Api/Application/Nodes/GetNodesRequest.php +++ b/app/Http/Requests/Api/Application/Nodes/GetNodesRequest.php @@ -2,12 +2,8 @@ namespace Pterodactyl\Http\Requests\Api\Application\Nodes; -use Pterodactyl\Services\Acl\Api\AdminAcl; use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest; class GetNodesRequest extends ApplicationApiRequest { - protected ?string $resource = AdminAcl::RESOURCE_NODES; - - protected int $permission = AdminAcl::READ; } diff --git a/app/Http/Requests/Api/Application/Nodes/StoreNodeRequest.php b/app/Http/Requests/Api/Application/Nodes/StoreNodeRequest.php index 4fe7054485..803f0e6a39 100644 --- a/app/Http/Requests/Api/Application/Nodes/StoreNodeRequest.php +++ b/app/Http/Requests/Api/Application/Nodes/StoreNodeRequest.php @@ -2,48 +2,50 @@ namespace Pterodactyl\Http\Requests\Api\Application\Nodes; +use Illuminate\Support\Arr; use Pterodactyl\Models\Node; -use Pterodactyl\Services\Acl\Api\AdminAcl; use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest; class StoreNodeRequest extends ApplicationApiRequest { - protected ?string $resource = AdminAcl::RESOURCE_NODES; - - protected int $permission = AdminAcl::WRITE; - /** * Validation rules to apply to this request. */ public function rules(array $rules = null): array { return collect($rules ?? Node::getRules())->only([ - 'public', 'name', + 'description', 'location_id', + 'database_host_id', 'fqdn', 'scheme', 'behind_proxy', - 'maintenance_mode', + 'public', + + 'listen_port_http', + 'public_port_http', + 'listen_port_sftp', + 'public_port_sftp', + 'memory', 'memory_overallocate', 'disk', 'disk_overallocate', + 'upload_size', - 'daemonListen', - 'daemonSFTP', - 'daemonBase', + 'daemon_base', ])->mapWithKeys(function ($value, $key) { - $key = ($key === 'daemonSFTP') ? 'daemonSftp' : $key; - return [snake_case($key) => $value]; })->toArray(); } /** * Fields to rename for clarity in the API response. + * + * @return array */ - public function attributes(): array + public function attributes() { return [ 'daemon_base' => 'Daemon Base Path', @@ -56,15 +58,20 @@ public function attributes(): array /** * Change the formatting of some data keys in the validated response data * to match what the application expects in the services. + * + * @param string|null $key + * @param string|array|null $default + * + * @return mixed */ - public function validated($key = null, $default = null): array + public function validated($key = null, $default = null) { $response = parent::validated(); - $response['daemonListen'] = $response['daemon_listen']; - $response['daemonSFTP'] = $response['daemon_sftp']; - $response['daemonBase'] = $response['daemon_base'] ?? (new Node())->getAttribute('daemonBase'); + $response['daemon_base'] = $response['daemon_base'] ?? Node::DEFAULT_DAEMON_BASE; - unset($response['daemon_base'], $response['daemon_listen'], $response['daemon_sftp']); + if (!is_null($key)) { + return Arr::get($response, $key, $default); + } return $response; } diff --git a/app/Http/Requests/Api/Application/Nodes/UpdateNodeRequest.php b/app/Http/Requests/Api/Application/Nodes/UpdateNodeRequest.php index de6b7b45c9..05daebc2eb 100644 --- a/app/Http/Requests/Api/Application/Nodes/UpdateNodeRequest.php +++ b/app/Http/Requests/Api/Application/Nodes/UpdateNodeRequest.php @@ -6,15 +6,8 @@ class UpdateNodeRequest extends StoreNodeRequest { - /** - * Apply validation rules to this request. Uses the parent class rules() - * function but passes in the rules for updating rather than creating. - */ public function rules(array $rules = null): array { - /** @var Node $node */ - $node = $this->route()->parameter('node'); - - return parent::rules(Node::getRulesForUpdate($node->id)); + return parent::rules($rules ?? Node::getRulesForUpdate($this->route()->parameter('node')->id)); } } diff --git a/app/Http/Requests/Api/Application/Roles/DeleteRoleRequest.php b/app/Http/Requests/Api/Application/Roles/DeleteRoleRequest.php new file mode 100644 index 0000000000..5f6cd34b53 --- /dev/null +++ b/app/Http/Requests/Api/Application/Roles/DeleteRoleRequest.php @@ -0,0 +1,9 @@ +route()->parameter('role')->id); + } +} diff --git a/app/Http/Requests/Api/Application/Servers/Databases/GetServerDatabaseRequest.php b/app/Http/Requests/Api/Application/Servers/Databases/GetServerDatabaseRequest.php index 01df4af328..dd1dd2fd1b 100644 --- a/app/Http/Requests/Api/Application/Servers/Databases/GetServerDatabaseRequest.php +++ b/app/Http/Requests/Api/Application/Servers/Databases/GetServerDatabaseRequest.php @@ -2,12 +2,8 @@ namespace Pterodactyl\Http\Requests\Api\Application\Servers\Databases; -use Pterodactyl\Services\Acl\Api\AdminAcl; use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest; class GetServerDatabaseRequest extends ApplicationApiRequest { - protected ?string $resource = AdminAcl::RESOURCE_SERVER_DATABASES; - - protected int $permission = AdminAcl::READ; } diff --git a/app/Http/Requests/Api/Application/Servers/Databases/GetServerDatabasesRequest.php b/app/Http/Requests/Api/Application/Servers/Databases/GetServerDatabasesRequest.php index ce72bbc20f..74f9422783 100644 --- a/app/Http/Requests/Api/Application/Servers/Databases/GetServerDatabasesRequest.php +++ b/app/Http/Requests/Api/Application/Servers/Databases/GetServerDatabasesRequest.php @@ -2,12 +2,8 @@ namespace Pterodactyl\Http\Requests\Api\Application\Servers\Databases; -use Pterodactyl\Services\Acl\Api\AdminAcl; use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest; class GetServerDatabasesRequest extends ApplicationApiRequest { - protected ?string $resource = AdminAcl::RESOURCE_SERVER_DATABASES; - - protected int $permission = AdminAcl::READ; } diff --git a/app/Http/Requests/Api/Application/Servers/Databases/ServerDatabaseWriteRequest.php b/app/Http/Requests/Api/Application/Servers/Databases/ServerDatabaseWriteRequest.php index 66cec82c39..827d68c555 100644 --- a/app/Http/Requests/Api/Application/Servers/Databases/ServerDatabaseWriteRequest.php +++ b/app/Http/Requests/Api/Application/Servers/Databases/ServerDatabaseWriteRequest.php @@ -2,9 +2,6 @@ namespace Pterodactyl\Http\Requests\Api\Application\Servers\Databases; -use Pterodactyl\Services\Acl\Api\AdminAcl; - class ServerDatabaseWriteRequest extends GetServerDatabasesRequest { - protected int $permission = AdminAcl::WRITE; } diff --git a/app/Http/Requests/Api/Application/Servers/Databases/StoreServerDatabaseRequest.php b/app/Http/Requests/Api/Application/Servers/Databases/StoreServerDatabaseRequest.php index f52bed805d..17368cd083 100644 --- a/app/Http/Requests/Api/Application/Servers/Databases/StoreServerDatabaseRequest.php +++ b/app/Http/Requests/Api/Application/Servers/Databases/StoreServerDatabaseRequest.php @@ -2,26 +2,18 @@ namespace Pterodactyl\Http\Requests\Api\Application\Servers\Databases; +use Illuminate\Support\Arr; use Webmozart\Assert\Assert; use Pterodactyl\Models\Server; use Illuminate\Validation\Rule; use Illuminate\Database\Query\Builder; -use Pterodactyl\Services\Acl\Api\AdminAcl; use Pterodactyl\Services\Databases\DatabaseManagementService; use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest; class StoreServerDatabaseRequest extends ApplicationApiRequest { - protected ?string $resource = AdminAcl::RESOURCE_SERVER_DATABASES; - - protected int $permission = AdminAcl::WRITE; - - /** - * Validation rules for database creation. - */ public function rules(): array { - /** @var Server $server */ $server = $this->route()->parameter('server'); return [ @@ -40,20 +32,22 @@ public function rules(): array } /** - * Return data formatted in the correct format for the service to consume. + * @param string|null $key + * @param string|array|null $default + * + * @return mixed */ - public function validated($key = null, $default = null): array + public function validated($key = null, $default = null) { - return [ + $data = [ 'database' => $this->input('database'), 'remote' => $this->input('remote'), 'database_host_id' => $this->input('host'), ]; + + return is_null($key) ? $data : Arr::get($data, $key, $default); } - /** - * Format error messages in a more understandable format for API output. - */ public function attributes(): array { return [ @@ -63,12 +57,8 @@ public function attributes(): array ]; } - /** - * Returns the database name in the expected format. - */ public function databaseName(): string { - /** @var Server $server */ $server = $this->route()->parameter('server'); Assert::isInstanceOf($server, Server::class); diff --git a/app/Http/Requests/Api/Application/Servers/GetExternalServerRequest.php b/app/Http/Requests/Api/Application/Servers/GetExternalServerRequest.php index 50c9dabf82..790f557989 100644 --- a/app/Http/Requests/Api/Application/Servers/GetExternalServerRequest.php +++ b/app/Http/Requests/Api/Application/Servers/GetExternalServerRequest.php @@ -2,12 +2,8 @@ namespace Pterodactyl\Http\Requests\Api\Application\Servers; -use Pterodactyl\Services\Acl\Api\AdminAcl; use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest; class GetExternalServerRequest extends ApplicationApiRequest { - protected ?string $resource = AdminAcl::RESOURCE_SERVERS; - - protected int $permission = AdminAcl::READ; } diff --git a/app/Http/Requests/Api/Application/Servers/GetServerRequest.php b/app/Http/Requests/Api/Application/Servers/GetServerRequest.php index 63c4ea86a2..2f4f417cd8 100644 --- a/app/Http/Requests/Api/Application/Servers/GetServerRequest.php +++ b/app/Http/Requests/Api/Application/Servers/GetServerRequest.php @@ -2,12 +2,8 @@ namespace Pterodactyl\Http\Requests\Api\Application\Servers; -use Pterodactyl\Services\Acl\Api\AdminAcl; use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest; class GetServerRequest extends ApplicationApiRequest { - protected ?string $resource = AdminAcl::RESOURCE_SERVERS; - - protected int $permission = AdminAcl::READ; } diff --git a/app/Http/Requests/Api/Application/Servers/ServerWriteRequest.php b/app/Http/Requests/Api/Application/Servers/ServerWriteRequest.php index df2d76cd39..e8d01a1157 100644 --- a/app/Http/Requests/Api/Application/Servers/ServerWriteRequest.php +++ b/app/Http/Requests/Api/Application/Servers/ServerWriteRequest.php @@ -2,12 +2,8 @@ namespace Pterodactyl\Http\Requests\Api\Application\Servers; -use Pterodactyl\Services\Acl\Api\AdminAcl; use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest; class ServerWriteRequest extends ApplicationApiRequest { - protected ?string $resource = AdminAcl::RESOURCE_SERVERS; - - protected int $permission = AdminAcl::WRITE; } diff --git a/app/Http/Requests/Api/Application/Servers/StoreServerRequest.php b/app/Http/Requests/Api/Application/Servers/StoreServerRequest.php index a9d0ecbed9..6a89567298 100644 --- a/app/Http/Requests/Api/Application/Servers/StoreServerRequest.php +++ b/app/Http/Requests/Api/Application/Servers/StoreServerRequest.php @@ -2,22 +2,12 @@ namespace Pterodactyl\Http\Requests\Api\Application\Servers; +use Illuminate\Support\Arr; use Pterodactyl\Models\Server; -use Illuminate\Validation\Rule; -use Illuminate\Validation\Validator; -use Pterodactyl\Services\Acl\Api\AdminAcl; -use Pterodactyl\Models\Objects\DeploymentObject; use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest; class StoreServerRequest extends ApplicationApiRequest { - protected ?string $resource = AdminAcl::RESOURCE_SERVERS; - - protected int $permission = AdminAcl::WRITE; - - /** - * Rules to be applied to this request. - */ public function rules(): array { $rules = Server::getRules(); @@ -26,15 +16,9 @@ public function rules(): array 'external_id' => $rules['external_id'], 'name' => $rules['name'], 'description' => array_merge(['nullable'], $rules['description']), - 'user' => $rules['owner_id'], - 'egg' => $rules['egg_id'], - 'docker_image' => $rules['image'], - 'startup' => $rules['startup'], - 'environment' => 'present|array', - 'skip_scripts' => 'sometimes|boolean', - 'oom_disabled' => 'sometimes|boolean', + 'owner_id' => $rules['owner_id'], + 'node_id' => $rules['node_id'], - // Resource limitations 'limits' => 'required|array', 'limits.memory' => $rules['memory'], 'limits.swap' => $rules['swap'], @@ -42,110 +26,64 @@ public function rules(): array 'limits.io' => $rules['io'], 'limits.threads' => $rules['threads'], 'limits.cpu' => $rules['cpu'], + 'limits.oom_killer' => 'required|boolean', - // Application Resource Limits 'feature_limits' => 'required|array', - 'feature_limits.databases' => $rules['database_limit'], 'feature_limits.allocations' => $rules['allocation_limit'], 'feature_limits.backups' => $rules['backup_limit'], + 'feature_limits.databases' => $rules['database_limit'], - // Placeholders for rules added in withValidator() function. - 'allocation.default' => '', - 'allocation.additional.*' => '', - - // Automatic deployment rules - 'deploy' => 'sometimes|required|array', - 'deploy.locations' => 'array', - 'deploy.locations.*' => 'integer|min:1', - 'deploy.dedicated_ip' => 'required_with:deploy,boolean', - 'deploy.port_range' => 'array', - 'deploy.port_range.*' => 'string', + 'allocation.default' => 'required|bail|integer|exists:allocations,id', + 'allocation.additional.*' => 'integer|exists:allocations,id', - 'start_on_completion' => 'sometimes|boolean', + 'startup' => $rules['startup'], + 'environment' => 'present|array', + 'egg_id' => $rules['egg_id'], + 'image' => $rules['image'], + 'skip_scripts' => 'present|boolean', ]; } /** - * Normalize the data into a format that can be consumed by the service. + * @param string|null $key + * @param string|array|null $default + * + * @return array */ - public function validated($key = null, $default = null): array + public function validated($key = null, $default = null) { $data = parent::validated(); - return [ + $response = [ 'external_id' => array_get($data, 'external_id'), 'name' => array_get($data, 'name'), 'description' => array_get($data, 'description'), - 'owner_id' => array_get($data, 'user'), - 'egg_id' => array_get($data, 'egg'), - 'image' => array_get($data, 'docker_image'), - 'startup' => array_get($data, 'startup'), - 'environment' => array_get($data, 'environment'), + 'owner_id' => array_get($data, 'owner_id'), + 'node_id' => array_get($data, 'node_id'), + 'memory' => array_get($data, 'limits.memory'), 'swap' => array_get($data, 'limits.swap'), 'disk' => array_get($data, 'limits.disk'), 'io' => array_get($data, 'limits.io'), - 'cpu' => array_get($data, 'limits.cpu'), 'threads' => array_get($data, 'limits.threads'), - 'skip_scripts' => array_get($data, 'skip_scripts', false), - 'allocation_id' => array_get($data, 'allocation.default'), - 'allocation_additional' => array_get($data, 'allocation.additional'), - 'start_on_completion' => array_get($data, 'start_on_completion', false), - 'database_limit' => array_get($data, 'feature_limits.databases'), + 'cpu' => array_get($data, 'limits.cpu'), + 'oom_disabled' => !array_get($data, 'limits.oom_killer'), + 'allocation_limit' => array_get($data, 'feature_limits.allocations'), 'backup_limit' => array_get($data, 'feature_limits.backups'), - 'oom_disabled' => array_get($data, 'oom_disabled'), - ]; - } - - /* - * Run validation after the rules above have been applied. - * - * @param \Illuminate\Validation\Validator $validator - */ - public function withValidator(Validator $validator): void - { - $validator->sometimes('allocation.default', [ - 'required', 'integer', 'bail', - Rule::exists('allocations', 'id')->where(function ($query) { - $query->whereNull('server_id'); - }), - ], function ($input) { - return !$input->deploy; - }); - - $validator->sometimes('allocation.additional.*', [ - 'integer', - Rule::exists('allocations', 'id')->where(function ($query) { - $query->whereNull('server_id'); - }), - ], function ($input) { - return !$input->deploy; - }); - - $validator->sometimes('deploy.locations', 'present', function ($input) { - return $input->deploy; - }); - - $validator->sometimes('deploy.port_range', 'present', function ($input) { - return $input->deploy; - }); - } + 'database_limit' => array_get($data, 'feature_limits.databases'), - /** - * Return a deployment object that can be passed to the server creation service. - */ - public function getDeploymentObject(): ?DeploymentObject - { - if (is_null($this->input('deploy'))) { - return null; - } + 'allocation_id' => array_get($data, 'allocation.default'), + 'allocation_additional' => array_get($data, 'allocation.additional'), - $object = new DeploymentObject(); - $object->setDedicated($this->input('deploy.dedicated_ip', false)); - $object->setLocations($this->input('deploy.locations', [])); - $object->setPorts($this->input('deploy.port_range', [])); + 'startup' => array_get($data, 'startup'), + 'environment' => array_get($data, 'environment'), + 'egg_id' => array_get($data, 'egg_id'), + 'image' => array_get($data, 'image'), + 'skip_scripts' => array_get($data, 'skip_scripts'), + 'start_on_completion' => array_get($data, 'start_on_completion', false), + ]; - return $object; + return is_null($key) ? $response : Arr::get($response, $key, $default); } } diff --git a/app/Http/Requests/Api/Application/Servers/UpdateServerBuildConfigurationRequest.php b/app/Http/Requests/Api/Application/Servers/UpdateServerBuildConfigurationRequest.php index f1c977f11f..1e2f120510 100644 --- a/app/Http/Requests/Api/Application/Servers/UpdateServerBuildConfigurationRequest.php +++ b/app/Http/Requests/Api/Application/Servers/UpdateServerBuildConfigurationRequest.php @@ -2,6 +2,7 @@ namespace Pterodactyl\Http\Requests\Api\Application\Servers; +use Illuminate\Support\Arr; use Pterodactyl\Models\Server; use Illuminate\Support\Collection; @@ -12,7 +13,7 @@ class UpdateServerBuildConfigurationRequest extends ServerWriteRequest */ public function rules(): array { - $rules = Server::getRulesForUpdate($this->parameter('server', Server::class)); + $rules = Server::getRulesForUpdate($this->route()->parameter('server')->id); return [ 'allocation' => $rules['allocation_id'], @@ -26,7 +27,7 @@ public function rules(): array 'limits.threads' => $this->requiredToOptional('threads', $rules['threads'], true), 'limits.disk' => $this->requiredToOptional('disk', $rules['disk'], true), - // Legacy rules to maintain backwards compatable API support without requiring + // Legacy rules to maintain backwards compatible API support without requiring // a major version bump. // // @see https://github.com/pterodactyl/panel/issues/1500 @@ -51,8 +52,13 @@ public function rules(): array /** * Convert the allocation field into the expected format for the service handler. + * + * @param string|null $key + * @param string|array|null $default + * + * @return mixed */ - public function validated($key = null, $default = null): array + public function validated($key = null, $default = null) { $data = parent::validated(); @@ -71,13 +77,19 @@ public function validated($key = null, $default = null): array unset($data['limits']); } + if (!is_null($key)) { + return Arr::get($data, $key, $default); + } + return $data; } /** * Custom attributes to use in error message responses. + * + * @return array */ - public function attributes(): array + public function attributes() { return [ 'add_allocations' => 'allocations to add', @@ -95,9 +107,11 @@ public function attributes(): array * compatability with the old API endpoint while also supporting a more correct API * call. * + * @return array + * * @see https://github.com/pterodactyl/panel/issues/1500 */ - protected function requiredToOptional(string $field, array $rules, bool $limits = false): array + protected function requiredToOptional(string $field, array $rules, bool $limits = false) { if (!in_array('required', $rules)) { return $rules; diff --git a/app/Http/Requests/Api/Application/Servers/UpdateServerDetailsRequest.php b/app/Http/Requests/Api/Application/Servers/UpdateServerDetailsRequest.php index aecc1cf02c..a4551edcd3 100644 --- a/app/Http/Requests/Api/Application/Servers/UpdateServerDetailsRequest.php +++ b/app/Http/Requests/Api/Application/Servers/UpdateServerDetailsRequest.php @@ -2,6 +2,7 @@ namespace Pterodactyl\Http\Requests\Api\Application\Servers; +use Illuminate\Support\Arr; use Pterodactyl\Models\Server; class UpdateServerDetailsRequest extends ServerWriteRequest @@ -11,7 +12,7 @@ class UpdateServerDetailsRequest extends ServerWriteRequest */ public function rules(): array { - $rules = Server::getRulesForUpdate($this->parameter('server', Server::class)); + $rules = Server::getRulesForUpdate($this->route()->parameter('server')->id); return [ 'external_id' => $rules['external_id'], @@ -24,19 +25,24 @@ public function rules(): array /** * Convert the posted data into the correct format that is expected * by the application. + * + * @param string|null $key + * @param string|array|null $default */ - public function validated($key = null, $default = null): array + public function validated($key = null, $default = null) { - return [ + $data = [ 'external_id' => $this->input('external_id'), 'name' => $this->input('name'), 'owner_id' => $this->input('user'), 'description' => $this->input('description'), ]; + + return is_null($key) ? $data : Arr::get($data, $key, $default); } /** - * Rename some attributes in error messages to clarify the field + * Rename some of the attributes in error messages to clarify the field * being discussed. */ public function attributes(): array diff --git a/app/Http/Requests/Api/Application/Servers/UpdateServerRequest.php b/app/Http/Requests/Api/Application/Servers/UpdateServerRequest.php new file mode 100644 index 0000000000..a588c381c5 --- /dev/null +++ b/app/Http/Requests/Api/Application/Servers/UpdateServerRequest.php @@ -0,0 +1,77 @@ + $rules['external_id'], + 'name' => $rules['name'], + 'description' => array_merge(['nullable'], $rules['description']), + 'owner_id' => $rules['owner_id'], + + 'limits' => 'sometimes|array', + 'limits.memory' => $rules['memory'], + 'limits.swap' => $rules['swap'], + 'limits.disk' => $rules['disk'], + 'limits.io' => $rules['io'], + 'limits.threads' => $rules['threads'], + 'limits.cpu' => $rules['cpu'], + 'limits.oom_killer' => 'sometimes|boolean', + + 'feature_limits' => 'required|array', + 'feature_limits.allocations' => $rules['allocation_limit'], + 'feature_limits.backups' => $rules['backup_limit'], + 'feature_limits.databases' => $rules['database_limit'], + + 'allocation_id' => 'bail|exists:allocations,id', + 'add_allocations' => 'bail|array', + 'add_allocations.*' => 'integer', + 'remove_allocations' => 'bail|array', + 'remove_allocations.*' => 'integer', + ]; + } + + /** + * @param string|null $key + * @param string|array|null $default + * + * @return mixed + */ + public function validated($key = null, $default = null) + { + $data = parent::validated(); + $response = [ + 'external_id' => array_get($data, 'external_id'), + 'name' => array_get($data, 'name'), + 'description' => array_get($data, 'description'), + 'owner_id' => array_get($data, 'owner_id'), + + 'memory' => array_get($data, 'limits.memory'), + 'swap' => array_get($data, 'limits.swap'), + 'disk' => array_get($data, 'limits.disk'), + 'io' => array_get($data, 'limits.io'), + 'threads' => array_get($data, 'limits.threads'), + 'cpu' => array_get($data, 'limits.cpu'), + 'oom_disabled' => !array_get($data, 'limits.oom_killer'), + + 'allocation_limit' => array_get($data, 'feature_limits.allocations'), + 'backup_limit' => array_get($data, 'feature_limits.backups'), + 'database_limit' => array_get($data, 'feature_limits.databases'), + + 'allocation_id' => array_get($data, 'allocation_id'), + 'add_allocations' => array_get($data, 'add_allocations'), + 'remove_allocations' => array_get($data, 'remove_allocations'), + ]; + + return is_null($key) ? $response : Arr::get($response, $key, $default); + } +} diff --git a/app/Http/Requests/Api/Application/Servers/UpdateServerStartupRequest.php b/app/Http/Requests/Api/Application/Servers/UpdateServerStartupRequest.php index 985b10a6f6..d8f92a1f85 100644 --- a/app/Http/Requests/Api/Application/Servers/UpdateServerStartupRequest.php +++ b/app/Http/Requests/Api/Application/Servers/UpdateServerStartupRequest.php @@ -3,41 +3,20 @@ namespace Pterodactyl\Http\Requests\Api\Application\Servers; use Pterodactyl\Models\Server; -use Pterodactyl\Services\Acl\Api\AdminAcl; use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest; class UpdateServerStartupRequest extends ApplicationApiRequest { - protected ?string $resource = AdminAcl::RESOURCE_SERVERS; - - protected int $permission = AdminAcl::WRITE; - - /** - * Validation rules to run the input against. - */ public function rules(): array { - $data = Server::getRulesForUpdate($this->parameter('server', Server::class)); + $rules = Server::getRulesForUpdate($this->route()->parameter('server')->id); return [ - 'startup' => $data['startup'], + 'startup' => $rules['startup'], 'environment' => 'present|array', - 'egg' => $data['egg_id'], - 'image' => $data['image'], + 'egg_id' => $rules['egg_id'], + 'image' => $rules['image'], 'skip_scripts' => 'present|boolean', ]; } - - /** - * Return the validated data in a format that is expected by the service. - */ - public function validated($key = null, $default = null): array - { - $data = parent::validated(); - - return collect($data)->only(['startup', 'environment', 'skip_scripts'])->merge([ - 'egg_id' => array_get($data, 'egg'), - 'docker_image' => array_get($data, 'image'), - ])->toArray(); - } } diff --git a/app/Http/Requests/Api/Application/Users/DeleteUserRequest.php b/app/Http/Requests/Api/Application/Users/DeleteUserRequest.php index 5e840a1c02..a2e3841fb3 100644 --- a/app/Http/Requests/Api/Application/Users/DeleteUserRequest.php +++ b/app/Http/Requests/Api/Application/Users/DeleteUserRequest.php @@ -2,12 +2,8 @@ namespace Pterodactyl\Http\Requests\Api\Application\Users; -use Pterodactyl\Services\Acl\Api\AdminAcl; use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest; class DeleteUserRequest extends ApplicationApiRequest { - protected ?string $resource = AdminAcl::RESOURCE_USERS; - - protected int $permission = AdminAcl::WRITE; } diff --git a/app/Http/Requests/Api/Application/Users/GetExternalUserRequest.php b/app/Http/Requests/Api/Application/Users/GetExternalUserRequest.php index 0f44aed3ff..b26ef7661f 100644 --- a/app/Http/Requests/Api/Application/Users/GetExternalUserRequest.php +++ b/app/Http/Requests/Api/Application/Users/GetExternalUserRequest.php @@ -2,12 +2,8 @@ namespace Pterodactyl\Http\Requests\Api\Application\Users; -use Pterodactyl\Services\Acl\Api\AdminAcl; use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest; class GetExternalUserRequest extends ApplicationApiRequest { - protected ?string $resource = AdminAcl::RESOURCE_USERS; - - protected int $permission = AdminAcl::READ; } diff --git a/app/Http/Requests/Api/Application/Users/GetUserRequest.php b/app/Http/Requests/Api/Application/Users/GetUserRequest.php new file mode 100644 index 0000000000..4e16088a5d --- /dev/null +++ b/app/Http/Requests/Api/Application/Users/GetUserRequest.php @@ -0,0 +1,7 @@ +only([ + return collect($rules)->only([ 'external_id', 'email', 'username', 'password', - 'language', + 'admin_role_id', 'root_admin', ])->toArray(); - - $response['first_name'] = $rules['name_first']; - $response['last_name'] = $rules['name_last']; - - return $response; - } - - public function validated($key = null, $default = null): array - { - $data = parent::validated(); - - $data['name_first'] = $data['first_name']; - $data['name_last'] = $data['last_name']; - - unset($data['first_name'], $data['last_name']); - - return $data; - } - - /** - * Rename some fields to be more user friendly. - */ - public function attributes(): array - { - return [ - 'external_id' => 'Third Party Identifier', - 'name_first' => 'First Name', - 'name_last' => 'Last Name', - 'root_admin' => 'Root Administrator Status', - ]; } } diff --git a/app/Http/Requests/Api/Application/Users/UpdateUserRequest.php b/app/Http/Requests/Api/Application/Users/UpdateUserRequest.php index fa2e1291c7..ba6d9da2c9 100644 --- a/app/Http/Requests/Api/Application/Users/UpdateUserRequest.php +++ b/app/Http/Requests/Api/Application/Users/UpdateUserRequest.php @@ -6,13 +6,8 @@ class UpdateUserRequest extends StoreUserRequest { - /** - * Return the validation rules for this request. - */ public function rules(array $rules = null): array { - $userId = $this->parameter('user', User::class)->id; - - return parent::rules(User::getRulesForUpdate($userId)); + return parent::rules($rules ?? User::getRulesForUpdate($this->route()->parameter('user')->id)); } } diff --git a/app/Models/AdminRole.php b/app/Models/AdminRole.php new file mode 100644 index 0000000000..51b485495f --- /dev/null +++ b/app/Models/AdminRole.php @@ -0,0 +1,68 @@ + 'int', + 'permissions' => 'array', + ]; + + public static array $validationRules = [ + 'name' => 'required|string|max:64', + 'description' => 'nullable|string|max:255', + 'sort_id' => 'sometimes|numeric', + ]; + + /** + * @var bool + */ + public $timestamps = false; + + /** + * Gets the permissions associated with a admin role. + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function permissions() + { + return $this->hasMany(Permission::class); + } +} diff --git a/app/Models/AuditLog.php b/app/Models/AuditLog.php deleted file mode 100644 index f42acbfffb..0000000000 --- a/app/Models/AuditLog.php +++ /dev/null @@ -1,80 +0,0 @@ - 'required|uuid', - 'action' => 'required|string|max:191', - 'subaction' => 'nullable|string|max:191', - 'device' => 'array', - 'device.ip_address' => 'ip', - 'device.user_agent' => 'string', - 'metadata' => 'array', - ]; - - protected $table = 'audit_logs'; - - protected bool $immutableDates = true; - - protected $casts = [ - 'is_system' => 'bool', - 'device' => 'array', - 'metadata' => 'array', - ]; - - protected $guarded = [ - 'id', - 'created_at', - ]; - - public function user(): BelongsTo - { - return $this->belongsTo(User::class); - } - - public function server(): BelongsTo - { - return $this->belongsTo(Server::class); - } - - /** - * Creates a new AuditLog model and returns it, attaching device information and the - * currently authenticated user if available. This model is not saved at this point, so - * you can always make modifications to it as needed before saving. - * - * @deprecated - */ - public static function instance(string $action, array $metadata, bool $isSystem = false): self - { - /** @var \Illuminate\Http\Request $request */ - $request = Container::getInstance()->make('request'); - if ($isSystem || !$request instanceof Request) { - $request = null; - } - - return (new self())->fill([ - 'uuid' => Uuid::uuid4()->toString(), - 'is_system' => $isSystem, - 'user_id' => ($request && $request->user()) ? $request->user()->id : null, - 'server_id' => null, - 'action' => $action, - 'device' => $request ? [ - 'ip_address' => $request->getClientIp() ?? '127.0.0.1', - 'user_agent' => $request->userAgent() ?? '', - ] : [], - 'metadata' => $metadata, - ]); - } -} diff --git a/app/Models/Backup.php b/app/Models/Backup.php index 41c94fee26..500205b13a 100644 --- a/app/Models/Backup.php +++ b/app/Models/Backup.php @@ -22,7 +22,6 @@ * @property \Carbon\CarbonImmutable $updated_at * @property \Carbon\CarbonImmutable|null $deleted_at * @property \Pterodactyl\Models\Server $server - * @property \Pterodactyl\Models\AuditLog[] $audits */ class Backup extends Model { diff --git a/app/Models/Server.php b/app/Models/Server.php index 5f2d6a49ef..e8560512a7 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -35,7 +35,7 @@ * @property int $allocation_id * @property int $nest_id * @property int $egg_id - * @property string $startup + * @property string|null $startup * @property string $image * @property int|null $allocation_limit * @property int|null $database_limit @@ -55,6 +55,7 @@ * @property \Pterodactyl\Models\Egg|null $egg * @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\Mount[] $mounts * @property int|null $mounts_count + * @property \Pterodactyl\Models\Location $location * @property \Pterodactyl\Models\Nest $nest * @property \Pterodactyl\Models\Node $node * @property \Illuminate\Notifications\DatabaseNotificationCollection|\Illuminate\Notifications\DatabaseNotification[] $notifications diff --git a/app/Models/User.php b/app/Models/User.php index bef6da7817..50361af8bf 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -12,6 +12,7 @@ use Pterodactyl\Models\Traits\HasAccessTokens; use Illuminate\Auth\Passwords\CanResetPassword; use Illuminate\Database\Eloquent\Casts\Attribute; +use Illuminate\Database\Eloquent\Relations\HasOne; use Pterodactyl\Traits\Helpers\AvailableLanguages; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Foundation\Auth\Access\Authorizable; @@ -29,11 +30,10 @@ * @property string $uuid * @property string $username * @property string $email - * @property string|null $name_first - * @property string|null $name_last * @property string $password * @property string|null $remember_token * @property string $language + * @property int|null $admin_role_id * @property bool $root_admin * @property bool $use_totp * @property string|null $totp_secret @@ -119,8 +119,6 @@ class User extends Model implements 'external_id', 'username', 'email', - 'name_first', - 'name_last', 'password', 'language', 'use_totp', @@ -169,8 +167,6 @@ class User extends Model implements 'email' => 'required|email|between:1,191|unique:users,email', 'external_id' => 'sometimes|nullable|string|max:191|unique:users,external_id', 'username' => 'required|between:1,191|unique:users,username', - 'name_first' => 'required|string|between:1,191', - 'name_last' => 'required|string|between:1,191', 'password' => 'sometimes|nullable|string', 'root_admin' => 'boolean', 'language' => 'string', @@ -193,11 +189,15 @@ public static function getRules(): array } /** - * Return the user model in a format that can be passed over to Vue templates. + * Return the user model in a format that can be passed over to React templates. */ - public function toVueObject(): array + public function toReactObject(): array { - return Collection::make($this->toArray())->except(['id', 'external_id'])->toArray(); + $object = Collection::make($this->toArray())->except(['id', 'external_id'])->toArray(); + $object['avatar_url'] = $this->avatarURL(); + $object['role_name'] = $this->adminRoleName(); + + return $object; } /** @@ -231,6 +231,29 @@ public function getNameAttribute(): string return trim($this->name_first . ' ' . $this->name_last); } + public function avatarURL(): string + { + return 'https://www.gravatar.com/avatar/' . md5($this->email) . '.jpg'; + } + + /** + * Gets the name of the role assigned to a user. + */ + public function adminRoleName(): ?string + { + $role = $this->adminRole; + if (is_null($role)) { + return $this->root_admin ? 'None' : null; + } + + return $role->name; + } + + public function adminRole(): HasOne + { + return $this->hasOne(AdminRole::class, 'id', 'admin_role_id'); + } + /** * Returns all servers that a user owns. */ diff --git a/app/Policies/.gitkeep b/app/Policies/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index c008b0d545..1bb94d2581 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -6,8 +6,6 @@ use Illuminate\Support\Str; use Illuminate\Support\Facades\URL; use Illuminate\Pagination\Paginator; -use Illuminate\Support\Facades\View; -use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Schema; use Illuminate\Support\ServiceProvider; use Illuminate\Database\Eloquent\Relations\Relation; @@ -21,9 +19,6 @@ public function boot() { Schema::defaultStringLength(191); - View::share('appVersion', $this->versionData()['version'] ?? 'undefined'); - View::share('appIsGit', $this->versionData()['is_git'] ?? false); - Paginator::useBootstrap(); // If the APP_URL value is set with https:// make sure we force it here. Theoretically @@ -61,32 +56,4 @@ public function register() $this->app->register(SettingsServiceProvider::class); } } - - /** - * Return version information for the footer. - */ - protected function versionData(): array - { - return Cache::remember('git-version', 5, function () { - if (file_exists(base_path('.git/HEAD'))) { - $head = explode(' ', file_get_contents(base_path('.git/HEAD'))); - - if (array_key_exists(1, $head)) { - $path = base_path('.git/' . trim($head[1])); - } - } - - if (isset($path) && file_exists($path)) { - return [ - 'version' => substr(file_get_contents($path), 0, 8), - 'is_git' => true, - ]; - } - - return [ - 'version' => config('app.version'), - 'is_git' => false, - ]; - }); - } } diff --git a/app/Services/Eggs/EggParserService.php b/app/Services/Eggs/EggParserService.php index 6d8545bc2c..c442b8e09e 100644 --- a/app/Services/Eggs/EggParserService.php +++ b/app/Services/Eggs/EggParserService.php @@ -4,7 +4,6 @@ use Illuminate\Support\Arr; use Pterodactyl\Models\Egg; -use Illuminate\Http\UploadedFile; use Illuminate\Support\Collection; use Pterodactyl\Exceptions\Service\InvalidFileUploadException; @@ -13,17 +12,10 @@ class EggParserService /** * Takes an uploaded file and parses out the egg configuration from within. * - * @throws \JsonException * @throws \Pterodactyl\Exceptions\Service\InvalidFileUploadException */ - public function handle(UploadedFile $file): array + public function handle(array $parsed): array { - if ($file->getError() !== UPLOAD_ERR_OK || !$file->isFile()) { - throw new InvalidFileUploadException('The selected file is not valid and cannot be imported.'); - } - - /** @var array $parsed */ - $parsed = json_decode($file->openFile()->fread($file->getSize()), true, 512, JSON_THROW_ON_ERROR); if (!in_array(Arr::get($parsed, 'meta.version') ?? '', ['PTDL_v1', 'PTDL_v2'])) { throw new InvalidFileUploadException('The JSON file provided is not in a format that can be recognized.'); } diff --git a/app/Services/Eggs/Sharing/EggImporterService.php b/app/Services/Eggs/Sharing/EggImporterService.php index ecd6eadb6a..8be37f0ab7 100644 --- a/app/Services/Eggs/Sharing/EggImporterService.php +++ b/app/Services/Eggs/Sharing/EggImporterService.php @@ -6,28 +6,107 @@ use Illuminate\Support\Arr; use Pterodactyl\Models\Egg; use Pterodactyl\Models\Nest; +use Symfony\Component\Yaml\Yaml; use Illuminate\Http\UploadedFile; use Pterodactyl\Models\EggVariable; use Illuminate\Database\ConnectionInterface; +use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Services\Eggs\EggParserService; +use Symfony\Component\Yaml\Exception\ParseException; +use Pterodactyl\Exceptions\Service\Egg\BadJsonFormatException; +use Pterodactyl\Exceptions\Service\Egg\BadYamlFormatException; +use Pterodactyl\Exceptions\Service\InvalidFileUploadException; class EggImporterService { - public function __construct(protected ConnectionInterface $connection, protected EggParserService $parser) - { + public function __construct( + private ConnectionInterface $connection, + private EggParserService $eggParserService + ) { } /** * Take an uploaded JSON file and parse it into a new egg. * - * @throws \Pterodactyl\Exceptions\Service\InvalidFileUploadException|\Throwable + * @deprecated use `handleFile` or `handleContent` instead + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Pterodactyl\Exceptions\Service\Egg\BadJsonFormatException + * @throws \Pterodactyl\Exceptions\Service\InvalidFileUploadException + * @throws \Pterodactyl\Exceptions\Service\Egg\BadYamlFormatException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\DisplayException + */ + public function handle(UploadedFile $file, int $nestId): Egg + { + return $this->handleFile($nestId, $file); + } + + /** + * ? + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Pterodactyl\Exceptions\Service\Egg\BadJsonFormatException + * @throws \Pterodactyl\Exceptions\Service\InvalidFileUploadException + * @throws \Pterodactyl\Exceptions\Service\Egg\BadYamlFormatException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\DisplayException + */ + public function handleFile(int $nestId, UploadedFile $file): Egg + { + if ($file->getError() !== UPLOAD_ERR_OK || !$file->isFile()) { + throw new InvalidFileUploadException(sprintf('The selected file ["%s"] was not in a valid format to import. (is_file: %s is_valid: %s err_code: %s err: %s)', $file->getFilename(), $file->isFile() ? 'true' : 'false', $file->isValid() ? 'true' : 'false', $file->getError(), $file->getErrorMessage())); + } + + return $this->handleContent($nestId, $file->openFile()->fread($file->getSize()), 'application/json'); + } + + /** + * ? + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Pterodactyl\Exceptions\Service\InvalidFileUploadException + * @throws \Pterodactyl\Exceptions\Service\Egg\BadYamlFormatException + * @throws \Pterodactyl\Exceptions\Service\Egg\BadJsonFormatException + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + */ + public function handleContent(int $nestId, string $content, string $contentType): Egg + { + switch (true) { + case str_starts_with($contentType, 'application/json'): + $parsed = json_decode($content, true); + if (json_last_error() !== 0) { + throw new BadJsonFormatException(trans('exceptions.nest.importer.json_error', ['error' => json_last_error_msg()])); + } + + return $this->handleArray($nestId, $parsed); + case str_starts_with($contentType, 'application/yaml'): + try { + $parsed = Yaml::parse($content); + + return $this->handleArray($nestId, $parsed); + } catch (ParseException $exception) { + throw new BadYamlFormatException('There was an error while attempting to parse the YAML: ' . $exception->getMessage() . '.'); + } + default: + throw new DisplayException('unknown content type'); + } + } + + /** + * ? + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Pterodactyl\Exceptions\Service\InvalidFileUploadException */ - public function handle(UploadedFile $file, int $nest): Egg + private function handleArray(int $nestId, array $parsed): Egg { - $parsed = $this->parser->handle($file); + $parsed = $this->eggParserService->handle($parsed); /** @var \Pterodactyl\Models\Nest $nest */ - $nest = Nest::query()->with('eggs', 'eggs.variables')->findOrFail($nest); + $nest = Nest::query()->with('eggs', 'eggs.variables')->findOrFail($nestId); return $this->connection->transaction(function () use ($nest, $parsed) { $egg = (new Egg())->forceFill([ diff --git a/app/Services/Eggs/Sharing/EggUpdateImporterService.php b/app/Services/Eggs/Sharing/EggUpdateImporterService.php index 89a1f92873..ad17a16f0a 100644 --- a/app/Services/Eggs/Sharing/EggUpdateImporterService.php +++ b/app/Services/Eggs/Sharing/EggUpdateImporterService.php @@ -8,14 +8,18 @@ use Pterodactyl\Models\EggVariable; use Illuminate\Database\ConnectionInterface; use Pterodactyl\Services\Eggs\EggParserService; +use Pterodactyl\Exceptions\Service\InvalidFileUploadException; +use Pterodactyl\Exceptions\Service\Egg\BadJsonFormatException; class EggUpdateImporterService { /** * EggUpdateImporterService constructor. */ - public function __construct(protected ConnectionInterface $connection, protected EggParserService $parser) - { + public function __construct( + private ConnectionInterface $connection, + private EggParserService $eggParserService + ) { } /** @@ -25,10 +29,18 @@ public function __construct(protected ConnectionInterface $connection, protected */ public function handle(Egg $egg, UploadedFile $file): Egg { - $parsed = $this->parser->handle($file); + if ($file->getError() !== UPLOAD_ERR_OK || !$file->isFile()) { + throw new InvalidFileUploadException(sprintf('The selected file ["%s"] was not in a valid format to import. (is_file: %s is_valid: %s err_code: %s err: %s)', $file->getFilename(), $file->isFile() ? 'true' : 'false', $file->isValid() ? 'true' : 'false', $file->getError(), $file->getErrorMessage())); + } + + $parsed = json_decode($file->openFile()->fread($file->getSize()), true); + if (json_last_error() !== 0) { + throw new BadJsonFormatException(trans('exceptions.nest.importer.json_error', ['error' => json_last_error_msg()])); + } + $parsed = $this->eggParserService->handle($parsed); return $this->connection->transaction(function () use ($egg, $parsed) { - $egg = $this->parser->fillFromParsed($egg, $parsed); + $egg = $this->eggParserService->fillFromParsed($egg, $parsed); $egg->save(); // Update existing variables or create new ones. diff --git a/app/Services/Eggs/Variables/VariableUpdateService.php b/app/Services/Eggs/Variables/VariableUpdateService.php index ed70b260fe..5d380f4ac1 100644 --- a/app/Services/Eggs/Variables/VariableUpdateService.php +++ b/app/Services/Eggs/Variables/VariableUpdateService.php @@ -3,6 +3,7 @@ namespace Pterodactyl\Services\Eggs\Variables; use Illuminate\Support\Str; +use Pterodactyl\Models\Egg; use Pterodactyl\Models\EggVariable; use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Traits\Services\ValidatesValidationRules; @@ -34,24 +35,21 @@ protected function getValidator(): ValidationFactory * Update a specific egg variable. * * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException * @throws \Pterodactyl\Exceptions\Service\Egg\Variable\ReservedVariableNameException */ - public function handle(EggVariable $variable, array $data): mixed + public function handle(Egg $egg, array $data): void { if (!is_null(array_get($data, 'env_variable'))) { if (in_array(strtoupper(array_get($data, 'env_variable')), explode(',', EggVariable::RESERVED_ENV_NAMES))) { throw new ReservedVariableNameException(trans('exceptions.service.variables.reserved_name', ['name' => array_get($data, 'env_variable')])); } - $search = $this->repository->setColumns('id')->findCountWhere([ - ['env_variable', '=', $data['env_variable']], - ['egg_id', '=', $variable->egg_id], - ['id', '!=', $variable->id], - ]); + $count = $egg->variables() + ->where('egg_variables.env_variable', $data['env_variable']) + ->where('egg_variables.id', '!=', $data['id']) + ->count(); - if ($search > 0) { + if ($count > 0) { throw new DisplayException(trans('exceptions.service.variables.env_not_unique', ['name' => array_get($data, 'env_variable')])); } } @@ -66,13 +64,13 @@ public function handle(EggVariable $variable, array $data): mixed $options = array_get($data, 'options') ?? []; - return $this->repository->withoutFreshModel()->update($variable->id, [ + $egg->variables()->where('egg_variables.id', $data['id'])->update([ 'name' => $data['name'] ?? '', 'description' => $data['description'] ?? '', 'env_variable' => $data['env_variable'] ?? '', 'default_value' => $data['default_value'] ?? '', - 'user_viewable' => in_array('user_viewable', $options), - 'user_editable' => in_array('user_editable', $options), + 'user_viewable' => $data['user_viewable'], + 'user_editable' => $data['user_editable'], 'rules' => $data['rules'] ?? '', ]); } diff --git a/app/Services/Helpers/SoftwareVersionService.php b/app/Services/Helpers/SoftwareVersionService.php index 2122b397b1..5b8a5aa80e 100644 --- a/app/Services/Helpers/SoftwareVersionService.php +++ b/app/Services/Helpers/SoftwareVersionService.php @@ -3,46 +3,54 @@ namespace Pterodactyl\Services\Helpers; use Exception; -use GuzzleHttp\Client; use Carbon\CarbonImmutable; use Illuminate\Support\Arr; +use Illuminate\Support\Str; +use Illuminate\Support\Facades\Http; use Illuminate\Contracts\Cache\Repository as CacheRepository; use Pterodactyl\Exceptions\Service\Helper\CdnVersionFetchingException; class SoftwareVersionService { public const VERSION_CACHE_KEY = 'pterodactyl:versioning_data'; + public const GIT_VERSION_CACHE_KEY = 'pterodactyl:git_data'; private static array $result; /** * SoftwareVersionService constructor. */ - public function __construct( - protected CacheRepository $cache, - protected Client $client - ) { + public function __construct(private CacheRepository $cache) + { self::$result = $this->cacheVersionData(); } /** - * Get the latest version of the panel from the CDN servers. + * Return the current version of the panel that is being used. */ - public function getPanel(): string + public function getCurrentVersion(): string + { + return config('app.version'); + } + + /** + * Returns the latest version of the panel from the CDN servers. + */ + public function getLatestPanel(): string { return Arr::get(self::$result, 'panel') ?? 'error'; } /** - * Get the latest version of the daemon from the CDN servers. + * Returns the latest version of the Wings from the CDN servers. */ - public function getDaemon(): string + public function getLatestWings(): string { return Arr::get(self::$result, 'wings') ?? 'error'; } /** - * Get the URL to the discord server. + * Returns the URL to the discord server. */ public function getDiscord(): string { @@ -50,7 +58,7 @@ public function getDiscord(): string } /** - * Get the URL for donations. + * Returns the URL for donations. */ public function getDonations(): string { @@ -62,23 +70,80 @@ public function getDonations(): string */ public function isLatestPanel(): bool { - if (config('app.version') === 'canary') { + $version = $this->getCurrentVersion(); + if ($version === 'canary') { return true; } - return version_compare(config('app.version'), $this->getPanel()) >= 0; + return version_compare($version, $this->getLatestPanel()) >= 0; } /** * Determine if a passed daemon version string is the latest. */ - public function isLatestDaemon(string $version): bool + public function isLatestWings(string $version): bool { - if ($version === 'develop') { + if ($version === 'develop' || Str::startsWith($version, 'dev-')) { return true; } - return version_compare($version, $this->getDaemon()) >= 0; + return version_compare($version, $this->getLatestWings()) >= 0; + } + + /** + * ? + */ + public function getVersionData(): array + { + $versionData = $this->versionData(); + if ($versionData['is_git']) { + $git = $versionData['version']; + } else { + $git = null; + } + + return [ + 'panel' => [ + 'current' => $this->getCurrentVersion(), + 'latest' => $this->getLatestPanel(), + ], + + 'wings' => [ + 'latest' => $this->getLatestWings(), + ], + + 'git' => $git, + ]; + } + + /** + * Return version information for the footer. + */ + protected function versionData(): array + { + return $this->cache->remember(self::GIT_VERSION_CACHE_KEY, CarbonImmutable::now()->addSeconds(15), function () { + $configVersion = config()->get('app.version'); + + if (file_exists(base_path('.git/HEAD'))) { + $head = explode(' ', file_get_contents(base_path('.git/HEAD'))); + + if (array_key_exists(1, $head)) { + $path = base_path('.git/' . trim($head[1])); + } + } + + if (isset($path) && file_exists($path)) { + return [ + 'version' => substr(file_get_contents($path), 0, 8), + 'is_git' => true, + ]; + } + + return [ + 'version' => $configVersion, + 'is_git' => false, + ]; + }); } /** @@ -88,10 +153,10 @@ protected function cacheVersionData(): array { return $this->cache->remember(self::VERSION_CACHE_KEY, CarbonImmutable::now()->addMinutes(config('pterodactyl.cdn.cache_time', 60)), function () { try { - $response = $this->client->request('GET', config('pterodactyl.cdn.url')); + $response = Http::get(config('pterodactyl.cdn.url')); - if ($response->getStatusCode() === 200) { - return json_decode($response->getBody(), true); + if ($response->status() === 200) { + return json_decode($response->body(), true); } throw new CdnVersionFetchingException(); diff --git a/app/Services/Servers/BuildModificationService.php b/app/Services/Servers/BuildModificationService.php index b7a22fdaae..b068933b35 100644 --- a/app/Services/Servers/BuildModificationService.php +++ b/app/Services/Servers/BuildModificationService.php @@ -49,9 +49,9 @@ public function handle(Server $server, array $data): Server $merge = Arr::only($data, ['oom_disabled', 'memory', 'swap', 'io', 'cpu', 'threads', 'disk', 'allocation_id']); $server->forceFill(array_merge($merge, [ - 'database_limit' => Arr::get($data, 'database_limit', 0) ?? null, 'allocation_limit' => Arr::get($data, 'allocation_limit', 0) ?? null, 'backup_limit' => Arr::get($data, 'backup_limit', 0) ?? 0, + 'database_limit' => Arr::get($data, 'database_limit', 0) ?? null, ]))->saveOrFail(); return $server->refresh(); diff --git a/app/Services/Servers/ServerConfigurationStructureService.php b/app/Services/Servers/ServerConfigurationStructureService.php index 72f3277967..af6ba906f1 100644 --- a/app/Services/Servers/ServerConfigurationStructureService.php +++ b/app/Services/Servers/ServerConfigurationStructureService.php @@ -50,7 +50,7 @@ protected function returnCurrentFormat(Server $server): array ], 'suspended' => $server->isSuspended(), 'environment' => $this->environment->handle($server), - 'invocation' => $server->startup, + 'invocation' => !is_null($server->startup) ? $server->startup : $server->egg->startup, 'skip_egg_scripts' => $server->skip_scripts, 'build' => [ 'memory_limit' => $server->memory, @@ -63,18 +63,13 @@ protected function returnCurrentFormat(Server $server): array ], 'container' => [ 'image' => $server->image, - // This field is deprecated — use the value in the "build" block. - // - // TODO: remove this key in V2. - 'oom_disabled' => $server->oom_disabled, - 'requires_rebuild' => false, ], 'allocations' => [ - 'force_outgoing_ip' => $server->egg->force_outgoing_ip, 'default' => [ 'ip' => $server->allocation->ip, 'port' => $server->allocation->port, ], + 'force_outgoing_ip' => $server->egg->force_outgoing_ip, 'mappings' => $server->getAllocationMappings(), ], 'mounts' => $server->mounts->map(function (Mount $mount) { diff --git a/app/Transformers/Api/Application/AdminRoleTransformer.php b/app/Transformers/Api/Application/AdminRoleTransformer.php new file mode 100644 index 0000000000..7a561528a2 --- /dev/null +++ b/app/Transformers/Api/Application/AdminRoleTransformer.php @@ -0,0 +1,29 @@ + $model->id, + 'name' => $model->name, + 'description' => $model->description, + ]; + } +} diff --git a/app/Transformers/Api/Application/AllocationTransformer.php b/app/Transformers/Api/Application/AllocationTransformer.php index fcd65f98f0..4aafc99f8c 100644 --- a/app/Transformers/Api/Application/AllocationTransformer.php +++ b/app/Transformers/Api/Application/AllocationTransformer.php @@ -2,18 +2,14 @@ namespace Pterodactyl\Transformers\Api\Application; -use Pterodactyl\Models\Node; -use Pterodactyl\Models\Server; use League\Fractal\Resource\Item; use Pterodactyl\Models\Allocation; use League\Fractal\Resource\NullResource; use Pterodactyl\Services\Acl\Api\AdminAcl; +use Pterodactyl\Transformers\Api\Transformer; -class AllocationTransformer extends BaseTransformer +class AllocationTransformer extends Transformer { - /** - * Relationships that can be loaded onto allocation transformations. - */ protected array $availableIncludes = ['node', 'server']; /** @@ -27,22 +23,21 @@ public function getResourceName(): string /** * Return a generic transformed allocation array. */ - public function transform(Allocation $allocation): array + public function transform(Allocation $model): array { return [ - 'id' => $allocation->id, - 'ip' => $allocation->ip, - 'alias' => $allocation->ip_alias, - 'port' => $allocation->port, - 'notes' => $allocation->notes, - 'assigned' => !is_null($allocation->server_id), + 'id' => $model->id, + 'ip' => $model->ip, + 'alias' => $model->ip_alias, + 'port' => $model->port, + 'notes' => $model->notes, + 'server_id' => $model->server_id, + 'assigned' => !is_null($model->server_id), ]; } /** * Load the node relationship onto a given transformation. - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function includeNode(Allocation $allocation): Item|NullResource { @@ -50,17 +45,11 @@ public function includeNode(Allocation $allocation): Item|NullResource return $this->null(); } - return $this->item( - $allocation->node, - $this->makeTransformer(NodeTransformer::class), - Node::RESOURCE_NAME - ); + return $this->item($allocation->node, new NodeTransformer()); } /** * Load the server relationship onto a given transformation. - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function includeServer(Allocation $allocation): Item|NullResource { @@ -68,10 +57,6 @@ public function includeServer(Allocation $allocation): Item|NullResource return $this->null(); } - return $this->item( - $allocation->server, - $this->makeTransformer(ServerTransformer::class), - Server::RESOURCE_NAME - ); + return $this->item($allocation->server, new ServerTransformer()); } } diff --git a/app/Transformers/Api/Application/BaseTransformer.php b/app/Transformers/Api/Application/BaseTransformer.php deleted file mode 100644 index 723caa3b8b..0000000000 --- a/app/Transformers/Api/Application/BaseTransformer.php +++ /dev/null @@ -1,114 +0,0 @@ -call([$this, 'handle']); - } - } - - /** - * Return the resource name for the JSONAPI output. - */ - abstract public function getResourceName(): string; - - /** - * Sets the request on the instance. - */ - public function setRequest(Request $request): self - { - $this->request = $request; - - return $this; - } - - /** - * Returns a new transformer instance with the request set on the instance. - */ - public static function fromRequest(Request $request): BaseTransformer - { - return app(static::class)->setRequest($request); - } - - /** - * Determine if the API key loaded onto the transformer has permission - * to access a different resource. This is used when including other - * models on a transformation request. - * - * @deprecated — prefer $user->can/cannot methods - */ - protected function authorize(string $resource): bool - { - $allowed = [ApiKey::TYPE_ACCOUNT, ApiKey::TYPE_APPLICATION]; - - $token = $this->request->user()->currentAccessToken(); - if (!$token instanceof ApiKey || !in_array($token->key_type, $allowed)) { - return false; - } - - // If this is not a deprecated application token type we can only check that - // the user is a root admin at the moment. In a future release we'll be rolling - // out more specific permissions for keys. - if ($token->key_type === ApiKey::TYPE_ACCOUNT) { - return $this->request->user()->root_admin; - } - - return AdminAcl::check($token, $resource); - } - - /** - * Create a new instance of the transformer and pass along the currently - * set API key. - * - * @template T of \Pterodactyl\Transformers\Api\Application\BaseTransformer - * - * @param class-string $abstract - * - * @return T - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException - * - * @noinspection PhpDocSignatureInspection - */ - protected function makeTransformer(string $abstract) - { - Assert::subclassOf($abstract, self::class); - - return $abstract::fromRequest($this->request); - } - - /** - * Return an ISO-8601 formatted timestamp to use in the API response. - */ - protected function formatTimestamp(string $timestamp): string - { - return CarbonImmutable::createFromFormat(CarbonInterface::DEFAULT_TO_STRING_FORMAT, $timestamp) - ->setTimezone(self::RESPONSE_TIMEZONE) - ->toAtomString(); - } -} diff --git a/app/Transformers/Api/Application/DatabaseHostTransformer.php b/app/Transformers/Api/Application/DatabaseHostTransformer.php index 019fdf261d..c69f9dff37 100644 --- a/app/Transformers/Api/Application/DatabaseHostTransformer.php +++ b/app/Transformers/Api/Application/DatabaseHostTransformer.php @@ -2,17 +2,15 @@ namespace Pterodactyl\Transformers\Api\Application; -use Pterodactyl\Models\Database; use Pterodactyl\Models\DatabaseHost; use League\Fractal\Resource\Collection; use League\Fractal\Resource\NullResource; use Pterodactyl\Services\Acl\Api\AdminAcl; +use Pterodactyl\Transformers\Api\Transformer; -class DatabaseHostTransformer extends BaseTransformer +class DatabaseHostTransformer extends Transformer { - protected array $availableIncludes = [ - 'databases', - ]; + protected array $availableIncludes = ['databases']; /** * Return the resource name for the JSONAPI output. @@ -33,16 +31,14 @@ public function transform(DatabaseHost $model): array 'host' => $model->host, 'port' => $model->port, 'username' => $model->username, - 'node' => $model->node_id, - 'created_at' => $model->created_at->toAtomString(), - 'updated_at' => $model->updated_at->toAtomString(), + 'node_id' => $model->node_id, + 'created_at' => self::formatTimestamp($model->created_at), + 'updated_at' => self::formatTimestamp($model->updated_at), ]; } /** * Include the databases associated with this host. - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function includeDatabases(DatabaseHost $model): Collection|NullResource { @@ -50,8 +46,7 @@ public function includeDatabases(DatabaseHost $model): Collection|NullResource return $this->null(); } - $model->loadMissing('databases'); - - return $this->collection($model->getRelation('databases'), $this->makeTransformer(ServerDatabaseTransformer::class), Database::RESOURCE_NAME); + // TODO + return $this->collection($model->databases, new ServerDatabaseTransformer()); } } diff --git a/app/Transformers/Api/Application/EggTransformer.php b/app/Transformers/Api/Application/EggTransformer.php index 9ed5736b46..c98a5d16ac 100644 --- a/app/Transformers/Api/Application/EggTransformer.php +++ b/app/Transformers/Api/Application/EggTransformer.php @@ -2,26 +2,23 @@ namespace Pterodactyl\Transformers\Api\Application; -use Illuminate\Support\Arr; use Pterodactyl\Models\Egg; -use Pterodactyl\Models\Nest; -use Pterodactyl\Models\Server; use League\Fractal\Resource\Item; -use Pterodactyl\Models\EggVariable; use League\Fractal\Resource\Collection; use League\Fractal\Resource\NullResource; use Pterodactyl\Services\Acl\Api\AdminAcl; +use Pterodactyl\Transformers\Api\Transformer; -class EggTransformer extends BaseTransformer +class EggTransformer extends Transformer { /** * Relationships that can be loaded onto this transformation. */ protected array $availableIncludes = [ - 'nest', - 'servers', 'config', + 'nest', 'script', + 'servers', 'variables', ]; @@ -50,19 +47,14 @@ public function transform(Egg $model): array 'id' => $model->id, 'uuid' => $model->uuid, 'name' => $model->name, - 'nest' => $model->nest_id, + 'nest_id' => $model->nest_id, 'author' => $model->author, 'description' => $model->description, - // "docker_image" is deprecated, but left here to avoid breaking too many things at once - // in external software. We'll remove it down the road once things have gotten the chance - // to upgrade to using "docker_images". - 'docker_image' => count($model->docker_images) > 0 ? Arr::first($model->docker_images) : '', 'docker_images' => $model->docker_images, 'config' => [ 'files' => $files, 'startup' => json_decode($model->config_startup, true), 'stop' => $model->config_stop, - 'logs' => json_decode($model->config_logs, true), 'file_denylist' => $model->file_denylist, 'extends' => $model->config_from, ], @@ -74,43 +66,11 @@ public function transform(Egg $model): array 'container' => $model->script_container, 'extends' => $model->copy_script_from, ], - $model->getCreatedAtColumn() => $this->formatTimestamp($model->created_at), - $model->getUpdatedAtColumn() => $this->formatTimestamp($model->updated_at), + 'created_at' => self::formatTimestamp($model->created_at), + 'updated_at' => self::formatTimestamp($model->updated_at), ]; } - /** - * Include the Nest relationship for the given Egg in the transformation. - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException - */ - public function includeNest(Egg $model): Item|NullResource - { - if (!$this->authorize(AdminAcl::RESOURCE_NESTS)) { - return $this->null(); - } - - $model->loadMissing('nest'); - - return $this->item($model->getRelation('nest'), $this->makeTransformer(NestTransformer::class), Nest::RESOURCE_NAME); - } - - /** - * Include the Servers relationship for the given Egg in the transformation. - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException - */ - public function includeServers(Egg $model): Collection|NullResource - { - if (!$this->authorize(AdminAcl::RESOURCE_SERVERS)) { - return $this->null(); - } - - $model->loadMissing('servers'); - - return $this->collection($model->getRelation('servers'), $this->makeTransformer(ServerTransformer::class), Server::RESOURCE_NAME); - } - /** * Include more detailed information about the configuration if this Egg is * extending another. @@ -121,8 +81,6 @@ public function includeConfig(Egg $model): Item|NullResource return $this->null(); } - $model->loadMissing('configFrom'); - return $this->item($model, function (Egg $model) { return [ 'files' => json_decode($model->inherit_config_files), @@ -133,6 +91,18 @@ public function includeConfig(Egg $model): Item|NullResource }); } + /** + * Include the Nest relationship for the given Egg in the transformation. + */ + public function includeNest(Egg $model): Item|NullResource + { + if (!$this->authorize(AdminAcl::RESOURCE_NESTS)) { + return $this->null(); + } + + return $this->item($model->nest, new NestTransformer()); + } + /** * Include more detailed information about the script configuration if the * Egg is extending another. @@ -143,8 +113,6 @@ public function includeScript(Egg $model): Item|NullResource return $this->null(); } - $model->loadMissing('scriptFrom'); - return $this->item($model, function (Egg $model) { return [ 'privileged' => $model->script_is_privileged, @@ -155,10 +123,20 @@ public function includeScript(Egg $model): Item|NullResource }); } + /** + * Include the Servers relationship for the given Egg in the transformation. + */ + public function includeServers(Egg $model): Collection|NullResource + { + if (!$this->authorize(AdminAcl::RESOURCE_SERVERS)) { + return $this->null(); + } + + return $this->collection($model->servers, new ServerTransformer()); + } + /** * Include the variables that are defined for this Egg. - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function includeVariables(Egg $model): Collection|NullResource { @@ -168,10 +146,6 @@ public function includeVariables(Egg $model): Collection|NullResource $model->loadMissing('variables'); - return $this->collection( - $model->getRelation('variables'), - $this->makeTransformer(EggVariableTransformer::class), - EggVariable::RESOURCE_NAME - ); + return $this->collection($model->variables, new EggVariableTransformer()); } } diff --git a/app/Transformers/Api/Application/EggVariableTransformer.php b/app/Transformers/Api/Application/EggVariableTransformer.php index 2088806d5e..613b5768de 100644 --- a/app/Transformers/Api/Application/EggVariableTransformer.php +++ b/app/Transformers/Api/Application/EggVariableTransformer.php @@ -4,8 +4,9 @@ use Pterodactyl\Models\Egg; use Pterodactyl\Models\EggVariable; +use Pterodactyl\Transformers\Api\Transformer; -class EggVariableTransformer extends BaseTransformer +class EggVariableTransformer extends Transformer { /** * Return the resource name for the JSONAPI output. @@ -15,7 +16,10 @@ public function getResourceName(): string return Egg::RESOURCE_NAME; } - public function transform(EggVariable $model) + /** + * Transform egg variable into a representation for the application API. + */ + public function transform(EggVariable $model): array { return $model->toArray(); } diff --git a/app/Transformers/Api/Application/LocationTransformer.php b/app/Transformers/Api/Application/LocationTransformer.php index 8fea3feb31..5ef3e74d5c 100644 --- a/app/Transformers/Api/Application/LocationTransformer.php +++ b/app/Transformers/Api/Application/LocationTransformer.php @@ -6,8 +6,9 @@ use League\Fractal\Resource\Collection; use League\Fractal\Resource\NullResource; use Pterodactyl\Services\Acl\Api\AdminAcl; +use Pterodactyl\Transformers\Api\Transformer; -class LocationTransformer extends BaseTransformer +class LocationTransformer extends Transformer { /** * List of resources that can be included. @@ -25,46 +26,38 @@ public function getResourceName(): string /** * Return a generic transformed location array. */ - public function transform(Location $location): array + public function transform(Location $model): array { return [ - 'id' => $location->id, - 'short' => $location->short, - 'long' => $location->long, - $location->getUpdatedAtColumn() => $this->formatTimestamp($location->updated_at), - $location->getCreatedAtColumn() => $this->formatTimestamp($location->created_at), + 'id' => $model->id, + 'short' => $model->short, + 'long' => $model->long, + 'created_at' => self::formatTimestamp($model->created_at), + 'updated_at' => self::formatTimestamp($model->updated_at), ]; } /** * Return the nodes associated with this location. - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ - public function includeServers(Location $location): Collection|NullResource + public function includeNodes(Location $location): Collection|NullResource { - if (!$this->authorize(AdminAcl::RESOURCE_SERVERS)) { + if (!$this->authorize(AdminAcl::RESOURCE_NODES)) { return $this->null(); } - $location->loadMissing('servers'); - - return $this->collection($location->getRelation('servers'), $this->makeTransformer(ServerTransformer::class), 'server'); + return $this->collection($location->nodes, new NodeTransformer()); } /** * Return the nodes associated with this location. - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ - public function includeNodes(Location $location): Collection|NullResource + public function includeServers(Location $location): Collection|NullResource { - if (!$this->authorize(AdminAcl::RESOURCE_NODES)) { + if (!$this->authorize(AdminAcl::RESOURCE_SERVERS)) { return $this->null(); } - $location->loadMissing('nodes'); - - return $this->collection($location->getRelation('nodes'), $this->makeTransformer(NodeTransformer::class), 'node'); + return $this->collection($location->servers, new ServerTransformer()); } } diff --git a/app/Transformers/Api/Application/MountTransformer.php b/app/Transformers/Api/Application/MountTransformer.php new file mode 100644 index 0000000000..8c0d1f289c --- /dev/null +++ b/app/Transformers/Api/Application/MountTransformer.php @@ -0,0 +1,75 @@ + $model->id, + 'uuid' => $model->uuid, + 'name' => $model->name, + 'description' => $model->description, + 'source' => $model->source, + 'target' => $model->target, + 'read_only' => $model->read_only, + 'user_mountable' => $model->user_mountable, + ]; + } + + /** + * Return the eggs associated with this mount. + */ + public function includeEggs(Mount $mount): Collection|NullResource + { + if (!$this->authorize(AdminAcl::RESOURCE_EGGS)) { + return $this->null(); + } + + return $this->collection($mount->eggs, new EggTransformer()); + } + + /** + * Return the nodes associated with this mount. + */ + public function includeNodes(Mount $mount): Collection|NullResource + { + if (!$this->authorize(AdminAcl::RESOURCE_NODES)) { + return $this->null(); + } + + return $this->collection($mount->nodes, new NodeTransformer()); + } + + /** + * Return the servers associated with this mount. + */ + public function includeServers(Mount $mount): Collection|NullResource + { + if (!$this->authorize(AdminAcl::RESOURCE_SERVERS)) { + return $this->null(); + } + + return $this->collection($mount->servers, new ServerTransformer()); + } +} diff --git a/app/Transformers/Api/Application/NestTransformer.php b/app/Transformers/Api/Application/NestTransformer.php index 2f530d44e4..8521564c6a 100644 --- a/app/Transformers/Api/Application/NestTransformer.php +++ b/app/Transformers/Api/Application/NestTransformer.php @@ -2,21 +2,18 @@ namespace Pterodactyl\Transformers\Api\Application; -use Pterodactyl\Models\Egg; use Pterodactyl\Models\Nest; -use Pterodactyl\Models\Server; use League\Fractal\Resource\Collection; use League\Fractal\Resource\NullResource; use Pterodactyl\Services\Acl\Api\AdminAcl; +use Pterodactyl\Transformers\Api\Transformer; -class NestTransformer extends BaseTransformer +class NestTransformer extends Transformer { /** * Relationships that can be loaded onto this transformation. */ - protected array $availableIncludes = [ - 'eggs', 'servers', - ]; + protected array $availableIncludes = ['eggs', 'servers']; /** * Return the resource name for the JSONAPI output. @@ -34,16 +31,14 @@ public function transform(Nest $model): array { $response = $model->toArray(); - $response[$model->getUpdatedAtColumn()] = $this->formatTimestamp($model->updated_at); - $response[$model->getCreatedAtColumn()] = $this->formatTimestamp($model->created_at); + $response['created_at'] = self::formatTimestamp($model->created_at); + $response['updated_at'] = self::formatTimestamp($model->updated_at); return $response; } /** * Include the Eggs relationship on the given Nest model transformation. - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function includeEggs(Nest $model): Collection|NullResource { @@ -51,15 +46,11 @@ public function includeEggs(Nest $model): Collection|NullResource return $this->null(); } - $model->loadMissing('eggs'); - - return $this->collection($model->getRelation('eggs'), $this->makeTransformer(EggTransformer::class), Egg::RESOURCE_NAME); + return $this->collection($model->eggs, new EggTransformer()); } /** * Include the servers relationship on the given Nest model. - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function includeServers(Nest $model): Collection|NullResource { @@ -67,8 +58,6 @@ public function includeServers(Nest $model): Collection|NullResource return $this->null(); } - $model->loadMissing('servers'); - - return $this->collection($model->getRelation('servers'), $this->makeTransformer(ServerTransformer::class), Server::RESOURCE_NAME); + return $this->collection($model->servers, new ServerTransformer()); } } diff --git a/app/Transformers/Api/Application/NodeTransformer.php b/app/Transformers/Api/Application/NodeTransformer.php index 6347dfec39..de3fd63da4 100644 --- a/app/Transformers/Api/Application/NodeTransformer.php +++ b/app/Transformers/Api/Application/NodeTransformer.php @@ -7,8 +7,9 @@ use League\Fractal\Resource\Collection; use League\Fractal\Resource\NullResource; use Pterodactyl\Services\Acl\Api\AdminAcl; +use Pterodactyl\Transformers\Api\Transformer; -class NodeTransformer extends BaseTransformer +class NodeTransformer extends Transformer { /** * List of resources that can be included. @@ -27,9 +28,9 @@ public function getResourceName(): string * Return a node transformed into a format that can be consumed by the * external administrative API. */ - public function transform(Node $node): array + public function transform(Node $model): array { - $response = collect($node->toArray())->mapWithKeys(function ($value, $key) { + $response = collect($model->toArray())->mapWithKeys(function ($value, $key) { // I messed up early in 2016 when I named this column as poorly // as I did. This is the tragic result of my mistakes. $key = ($key === 'daemonSFTP') ? 'daemonSftp' : $key; @@ -37,10 +38,10 @@ public function transform(Node $node): array return [snake_case($key) => $value]; })->toArray(); - $response[$node->getUpdatedAtColumn()] = $this->formatTimestamp($node->updated_at); - $response[$node->getCreatedAtColumn()] = $this->formatTimestamp($node->created_at); + $response['created_at'] = self::formatTimestamp($model->created_at); + $response['updated_at'] = self::formatTimestamp($model->updated_at); - $resources = $node->servers()->select(['memory', 'disk'])->get(); + $resources = $model->servers()->select(['memory', 'disk'])->get(); $response['allocated_resources'] = [ 'memory' => $resources->sum('memory'), @@ -51,9 +52,7 @@ public function transform(Node $node): array } /** - * Return the nodes associated with this location. - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException + * Return the allocations associated with this node. */ public function includeAllocations(Node $node): Collection|NullResource { @@ -61,19 +60,11 @@ public function includeAllocations(Node $node): Collection|NullResource return $this->null(); } - $node->loadMissing('allocations'); - - return $this->collection( - $node->getRelation('allocations'), - $this->makeTransformer(AllocationTransformer::class), - 'allocation' - ); + return $this->collection($node->allocations, new AllocationTransformer()); } /** - * Return the nodes associated with this location. - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException + * Return the location associated with this node. */ public function includeLocation(Node $node): Item|NullResource { @@ -81,19 +72,11 @@ public function includeLocation(Node $node): Item|NullResource return $this->null(); } - $node->loadMissing('location'); - - return $this->item( - $node->getRelation('location'), - $this->makeTransformer(LocationTransformer::class), - 'location' - ); + return $this->item($node->location, new LocationTransformer()); } /** - * Return the nodes associated with this location. - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException + * Return the servers associated with this node. */ public function includeServers(Node $node): Collection|NullResource { @@ -101,12 +84,6 @@ public function includeServers(Node $node): Collection|NullResource return $this->null(); } - $node->loadMissing('servers'); - - return $this->collection( - $node->getRelation('servers'), - $this->makeTransformer(ServerTransformer::class), - 'server' - ); + return $this->collection($node->servers, new ServerTransformer()); } } diff --git a/app/Transformers/Api/Application/ServerDatabaseTransformer.php b/app/Transformers/Api/Application/ServerDatabaseTransformer.php index 2590482d92..159ac6b2d4 100644 --- a/app/Transformers/Api/Application/ServerDatabaseTransformer.php +++ b/app/Transformers/Api/Application/ServerDatabaseTransformer.php @@ -4,14 +4,14 @@ use Pterodactyl\Models\Database; use League\Fractal\Resource\Item; -use Pterodactyl\Models\DatabaseHost; use League\Fractal\Resource\NullResource; use Pterodactyl\Services\Acl\Api\AdminAcl; +use Pterodactyl\Transformers\Api\Transformer; use Illuminate\Contracts\Encryption\Encrypter; -class ServerDatabaseTransformer extends BaseTransformer +class ServerDatabaseTransformer extends Transformer { - protected array $availableIncludes = ['password', 'host']; + protected array $availableIncludes = ['host', 'password']; private Encrypter $encrypter; @@ -38,33 +38,19 @@ public function transform(Database $model): array { return [ 'id' => $model->id, - 'server' => $model->server_id, - 'host' => $model->database_host_id, - 'database' => $model->database, + 'database_host_id' => $model->database_host_id, + 'server_id' => $model->server_id, + 'name' => $model->database, 'username' => $model->username, 'remote' => $model->remote, 'max_connections' => $model->max_connections, - 'created_at' => $model->created_at->toAtomString(), - 'updated_at' => $model->updated_at->toAtomString(), + 'created_at' => self::formatTimestamp($model->created_at), + 'updated_at' => self::formatTimestamp($model->updated_at), ]; } - /** - * Include the database password in the request. - */ - public function includePassword(Database $model): Item - { - return $this->item($model, function (Database $model) { - return [ - 'password' => $this->encrypter->decrypt($model->password), - ]; - }, 'database_password'); - } - /** * Return the database host relationship for this server database. - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function includeHost(Database $model): Item|NullResource { @@ -72,12 +58,18 @@ public function includeHost(Database $model): Item|NullResource return $this->null(); } - $model->loadMissing('host'); + return $this->item($model->host, new DatabaseHostTransformer()); + } - return $this->item( - $model->getRelation('host'), - $this->makeTransformer(DatabaseHostTransformer::class), - DatabaseHost::RESOURCE_NAME - ); + /** + * Include the database password in the request. + */ + public function includePassword(Database $model): Item + { + return $this->item($model, function (Database $model) { + return [ + 'password' => $this->encrypter->decrypt($model->password), + ]; + }, 'database_password'); } } diff --git a/app/Transformers/Api/Application/ServerTransformer.php b/app/Transformers/Api/Application/ServerTransformer.php index e5db01fb21..ddc4f638cd 100644 --- a/app/Transformers/Api/Application/ServerTransformer.php +++ b/app/Transformers/Api/Application/ServerTransformer.php @@ -7,9 +7,10 @@ use League\Fractal\Resource\Collection; use League\Fractal\Resource\NullResource; use Pterodactyl\Services\Acl\Api\AdminAcl; +use Pterodactyl\Transformers\Api\Transformer; use Pterodactyl\Services\Servers\EnvironmentService; -class ServerTransformer extends BaseTransformer +class ServerTransformer extends Transformer { private EnvironmentService $environmentService; @@ -48,53 +49,47 @@ public function getResourceName(): string /** * Return a generic transformed server array. */ - public function transform(Server $server): array + public function transform(Server $model): array { return [ - 'id' => $server->getKey(), - 'external_id' => $server->external_id, - 'uuid' => $server->uuid, - 'identifier' => $server->uuidShort, - 'name' => $server->name, - 'description' => $server->description, - 'status' => $server->status, - // This field is deprecated, please use "status". - 'suspended' => $server->isSuspended(), + 'id' => $model->getKey(), + 'external_id' => $model->external_id, + 'uuid' => $model->uuid, + 'identifier' => $model->uuidShort, + 'name' => $model->name, + 'description' => $model->description, + 'status' => $model->status, 'limits' => [ - 'memory' => $server->memory, - 'swap' => $server->swap, - 'disk' => $server->disk, - 'io' => $server->io, - 'cpu' => $server->cpu, - 'threads' => $server->threads, - 'oom_disabled' => $server->oom_disabled, + 'cpu' => $model->cpu, + 'disk' => $model->disk, + 'io' => $model->io, + 'memory' => $model->memory, + 'oom_disabled' => $model->oom_disabled, + 'swap' => $model->swap, + 'threads' => $model->threads, ], 'feature_limits' => [ - 'databases' => $server->database_limit, - 'allocations' => $server->allocation_limit, - 'backups' => $server->backup_limit, + 'allocations' => $model->allocation_limit, + 'backups' => $model->backup_limit, + 'databases' => $model->database_limit, ], - 'user' => $server->owner_id, - 'node' => $server->node_id, - 'allocation' => $server->allocation_id, - 'nest' => $server->nest_id, - 'egg' => $server->egg_id, + 'user_id' => $model->owner_id, + 'node_id' => $model->node_id, + 'allocation_id' => $model->allocation_id, + 'nest_id' => $model->nest_id, + 'egg_id' => $model->egg_id, 'container' => [ - 'startup_command' => $server->startup, - 'image' => $server->image, - // This field is deprecated, please use "status". - 'installed' => $server->isInstalled() ? 1 : 0, - 'environment' => $this->environmentService->handle($server), + 'startup' => $model->startup, + 'image' => $model->image, + 'environment' => $this->environmentService->handle($model), ], - $server->getUpdatedAtColumn() => $this->formatTimestamp($server->updated_at), - $server->getCreatedAtColumn() => $this->formatTimestamp($server->created_at), + 'created_at' => self::formatTimestamp($model->created_at), + 'updated_at' => self::formatTimestamp($model->updated_at), ]; } /** * Return a generic array of allocations for this server. - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function includeAllocations(Server $server): Collection|NullResource { @@ -102,15 +97,11 @@ public function includeAllocations(Server $server): Collection|NullResource return $this->null(); } - $server->loadMissing('allocations'); - - return $this->collection($server->getRelation('allocations'), $this->makeTransformer(AllocationTransformer::class), 'allocation'); + return $this->collection($server->allocations, new AllocationTransformer()); } /** * Return a generic array of data about subusers for this server. - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function includeSubusers(Server $server): Collection|NullResource { @@ -118,15 +109,11 @@ public function includeSubusers(Server $server): Collection|NullResource return $this->null(); } - $server->loadMissing('subusers'); - - return $this->collection($server->getRelation('subusers'), $this->makeTransformer(SubuserTransformer::class), 'subuser'); + return $this->collection($server->subusers, new SubuserTransformer()); } /** * Return a generic array of data about subusers for this server. - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function includeUser(Server $server): Item|NullResource { @@ -134,15 +121,11 @@ public function includeUser(Server $server): Item|NullResource return $this->null(); } - $server->loadMissing('user'); - - return $this->item($server->getRelation('user'), $this->makeTransformer(UserTransformer::class), 'user'); + return $this->item($server->user, new UserTransformer()); } /** * Return a generic array with nest information for this server. - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function includeNest(Server $server): Item|NullResource { @@ -150,15 +133,11 @@ public function includeNest(Server $server): Item|NullResource return $this->null(); } - $server->loadMissing('nest'); - - return $this->item($server->getRelation('nest'), $this->makeTransformer(NestTransformer::class), 'nest'); + return $this->item($server->nest, new NestTransformer()); } /** * Return a generic array with egg information for this server. - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function includeEgg(Server $server): Item|NullResource { @@ -166,15 +145,11 @@ public function includeEgg(Server $server): Item|NullResource return $this->null(); } - $server->loadMissing('egg'); - - return $this->item($server->getRelation('egg'), $this->makeTransformer(EggTransformer::class), 'egg'); + return $this->item($server->egg, new EggTransformer()); } /** * Return a generic array of data about subusers for this server. - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function includeVariables(Server $server): Collection|NullResource { @@ -182,15 +157,11 @@ public function includeVariables(Server $server): Collection|NullResource return $this->null(); } - $server->loadMissing('variables'); - - return $this->collection($server->getRelation('variables'), $this->makeTransformer(ServerVariableTransformer::class), 'server_variable'); + return $this->collection($server->variables, new ServerVariableTransformer()); } /** * Return a generic array with location information for this server. - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function includeLocation(Server $server): Item|NullResource { @@ -198,15 +169,11 @@ public function includeLocation(Server $server): Item|NullResource return $this->null(); } - $server->loadMissing('location'); - - return $this->item($server->getRelation('location'), $this->makeTransformer(LocationTransformer::class), 'location'); + return $this->item($server->location, new LocationTransformer()); } /** * Return a generic array with node information for this server. - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function includeNode(Server $server): Item|NullResource { @@ -214,15 +181,11 @@ public function includeNode(Server $server): Item|NullResource return $this->null(); } - $server->loadMissing('node'); - - return $this->item($server->getRelation('node'), $this->makeTransformer(NodeTransformer::class), 'node'); + return $this->item($server->node, new NodeTransformer()); } /** * Return a generic array with database information for this server. - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function includeDatabases(Server $server): Collection|NullResource { @@ -230,8 +193,6 @@ public function includeDatabases(Server $server): Collection|NullResource return $this->null(); } - $server->loadMissing('databases'); - - return $this->collection($server->getRelation('databases'), $this->makeTransformer(ServerDatabaseTransformer::class), 'databases'); + return $this->collection($server->databases, new ServerDatabaseTransformer()); } } diff --git a/app/Transformers/Api/Application/ServerVariableTransformer.php b/app/Transformers/Api/Application/ServerVariableTransformer.php index e27d1e0135..7c3d1de75c 100644 --- a/app/Transformers/Api/Application/ServerVariableTransformer.php +++ b/app/Transformers/Api/Application/ServerVariableTransformer.php @@ -7,8 +7,9 @@ use Pterodactyl\Models\ServerVariable; use League\Fractal\Resource\NullResource; use Pterodactyl\Services\Acl\Api\AdminAcl; +use Pterodactyl\Transformers\Api\Transformer; -class ServerVariableTransformer extends BaseTransformer +class ServerVariableTransformer extends Transformer { /** * List of resources that can be included. @@ -26,15 +27,13 @@ public function getResourceName(): string /** * Return a generic transformed server variable array. */ - public function transform(EggVariable $variable): array + public function transform(EggVariable $model): array { - return $variable->toArray(); + return $model->toArray(); } /** * Return the parent service variable data. - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function includeParent(EggVariable $variable): Item|NullResource { @@ -42,8 +41,7 @@ public function includeParent(EggVariable $variable): Item|NullResource return $this->null(); } - $variable->loadMissing('variable'); - - return $this->item($variable->getRelation('variable'), $this->makeTransformer(EggVariableTransformer::class), 'variable'); + // TODO: what the fuck? + return $this->item($variable->variable, new EggVariableTransformer()); } } diff --git a/app/Transformers/Api/Application/SubuserTransformer.php b/app/Transformers/Api/Application/SubuserTransformer.php index 0a51d61d99..7b3ef7c0ba 100644 --- a/app/Transformers/Api/Application/SubuserTransformer.php +++ b/app/Transformers/Api/Application/SubuserTransformer.php @@ -6,13 +6,14 @@ use League\Fractal\Resource\Item; use League\Fractal\Resource\NullResource; use Pterodactyl\Services\Acl\Api\AdminAcl; +use Pterodactyl\Transformers\Api\Transformer; -class SubuserTransformer extends BaseTransformer +class SubuserTransformer extends Transformer { /** * List of resources that can be included. */ - protected array $availableIncludes = ['user', 'server']; + protected array $availableIncludes = ['server', 'user']; /** * Return the resource name for the JSONAPI output. @@ -25,47 +26,39 @@ public function getResourceName(): string /** * Return a transformed Subuser model that can be consumed by external services. */ - public function transform(Subuser $subuser): array + public function transform(Subuser $model): array { return [ - 'id' => $subuser->id, - 'user_id' => $subuser->user_id, - 'server_id' => $subuser->server_id, - 'permissions' => $subuser->permissions, - 'created_at' => $this->formatTimestamp($subuser->created_at), - 'updated_at' => $this->formatTimestamp($subuser->updated_at), + 'id' => $model->id, + 'user_id' => $model->user_id, + 'server_id' => $model->server_id, + 'permissions' => $model->permissions, + 'created_at' => self::formatTimestamp($model->created_at), + 'updated_at' => self::formatTimestamp($model->updated_at), ]; } /** - * Return a generic item of user for this subuser. - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException + * Return a generic item of server for this subuser. */ - public function includeUser(Subuser $subuser): Item|NullResource + public function includeServer(Subuser $subuser): Item|NullResource { - if (!$this->authorize(AdminAcl::RESOURCE_USERS)) { + if (!$this->authorize(AdminAcl::RESOURCE_SERVERS)) { return $this->null(); } - $subuser->loadMissing('user'); - - return $this->item($subuser->getRelation('user'), $this->makeTransformer(UserTransformer::class), 'user'); + return $this->item($subuser->server, new ServerTransformer()); } /** - * Return a generic item of server for this subuser. - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException + * Return a generic item of user for this subuser. */ - public function includeServer(Subuser $subuser): Item|NullResource + public function includeUser(Subuser $subuser): Item|NullResource { - if (!$this->authorize(AdminAcl::RESOURCE_SERVERS)) { + if (!$this->authorize(AdminAcl::RESOURCE_USERS)) { return $this->null(); } - $subuser->loadMissing('server'); - - return $this->item($subuser->getRelation('server'), $this->makeTransformer(ServerTransformer::class), 'server'); + return $this->item($subuser->user, new UserTransformer()); } } diff --git a/app/Transformers/Api/Application/UserTransformer.php b/app/Transformers/Api/Application/UserTransformer.php index 14e354b45d..c50a9d8c6e 100644 --- a/app/Transformers/Api/Application/UserTransformer.php +++ b/app/Transformers/Api/Application/UserTransformer.php @@ -6,8 +6,9 @@ use League\Fractal\Resource\Collection; use League\Fractal\Resource\NullResource; use Pterodactyl\Services\Acl\Api\AdminAcl; +use Pterodactyl\Transformers\Api\Transformer; -class UserTransformer extends BaseTransformer +class UserTransformer extends Transformer { /** * List of resources that can be included. @@ -25,28 +26,27 @@ public function getResourceName(): string /** * Return a transformed User model that can be consumed by external services. */ - public function transform(User $user): array + public function transform(User $model): array { return [ - 'id' => $user->id, - 'external_id' => $user->external_id, - 'uuid' => $user->uuid, - 'username' => $user->username, - 'email' => $user->email, - 'first_name' => $user->name_first, - 'last_name' => $user->name_last, - 'language' => $user->language, - 'root_admin' => (bool) $user->root_admin, - '2fa' => (bool) $user->use_totp, - 'created_at' => $this->formatTimestamp($user->created_at), - 'updated_at' => $this->formatTimestamp($user->updated_at), + 'id' => $model->id, + 'external_id' => $model->external_id, + 'uuid' => $model->uuid, + 'username' => $model->username, + 'email' => $model->email, + 'language' => $model->language, + 'root_admin' => (bool) $model->root_admin, + '2fa' => (bool) $model->use_totp, + 'avatar_url' => $model->avatarURL(), + 'admin_role_id' => $model->admin_role_id, + 'role_name' => $model->adminRoleName(), + 'created_at' => self::formatTimestamp($model->created_at), + 'updated_at' => self::formatTimestamp($model->updated_at), ]; } /** * Return the servers associated with this user. - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function includeServers(User $user): Collection|NullResource { @@ -54,8 +54,6 @@ public function includeServers(User $user): Collection|NullResource return $this->null(); } - $user->loadMissing('servers'); - - return $this->collection($user->getRelation('servers'), $this->makeTransformer(ServerTransformer::class), 'server'); + return $this->collection($user->servers, new ServerTransformer()); } } diff --git a/app/Transformers/Api/Client/AccountTransformer.php b/app/Transformers/Api/Client/AccountTransformer.php index 1a14555d83..68651d8f33 100644 --- a/app/Transformers/Api/Client/AccountTransformer.php +++ b/app/Transformers/Api/Client/AccountTransformer.php @@ -3,8 +3,9 @@ namespace Pterodactyl\Transformers\Api\Client; use Pterodactyl\Models\User; +use Pterodactyl\Transformers\Api\Transformer; -class AccountTransformer extends BaseClientTransformer +class AccountTransformer extends Transformer { /** * Return the resource name for the JSONAPI output. diff --git a/app/Transformers/Api/Client/ActivityLogTransformer.php b/app/Transformers/Api/Client/ActivityLogTransformer.php index 57c8ac30ed..dc0be9613b 100644 --- a/app/Transformers/Api/Client/ActivityLogTransformer.php +++ b/app/Transformers/Api/Client/ActivityLogTransformer.php @@ -4,10 +4,13 @@ use Illuminate\Support\Str; use Pterodactyl\Models\User; +use League\Fractal\Resource\Item; use Pterodactyl\Models\ActivityLog; use Illuminate\Database\Eloquent\Model; +use League\Fractal\Resource\NullResource; +use Pterodactyl\Transformers\Api\Transformer; -class ActivityLogTransformer extends BaseClientTransformer +class ActivityLogTransformer extends Transformer { protected array $availableIncludes = ['actor']; @@ -34,13 +37,13 @@ public function transform(ActivityLog $model): array ]; } - public function includeActor(ActivityLog $model) + public function includeActor(ActivityLog $model): Item|NullResource { if (!$model->actor instanceof User) { return $this->null(); } - return $this->item($model->actor, $this->makeTransformer(UserTransformer::class), User::RESOURCE_NAME); + return $this->item($model->actor, new UserTransformer()); } /** diff --git a/app/Transformers/Api/Client/AllocationTransformer.php b/app/Transformers/Api/Client/AllocationTransformer.php index 2e63e2bc9c..f39f2e3975 100644 --- a/app/Transformers/Api/Client/AllocationTransformer.php +++ b/app/Transformers/Api/Client/AllocationTransformer.php @@ -3,8 +3,9 @@ namespace Pterodactyl\Transformers\Api\Client; use Pterodactyl\Models\Allocation; +use Pterodactyl\Transformers\Api\Transformer; -class AllocationTransformer extends BaseClientTransformer +class AllocationTransformer extends Transformer { /** * Return the resource name for the JSONAPI output. diff --git a/app/Transformers/Api/Client/ApiKeyTransformer.php b/app/Transformers/Api/Client/ApiKeyTransformer.php index 92ee1a5c66..022b83ffc1 100644 --- a/app/Transformers/Api/Client/ApiKeyTransformer.php +++ b/app/Transformers/Api/Client/ApiKeyTransformer.php @@ -3,8 +3,9 @@ namespace Pterodactyl\Transformers\Api\Client; use Pterodactyl\Models\ApiKey; +use Pterodactyl\Transformers\Api\Transformer; -class ApiKeyTransformer extends BaseClientTransformer +class ApiKeyTransformer extends Transformer { /** * {@inheritdoc} diff --git a/app/Transformers/Api/Client/BackupTransformer.php b/app/Transformers/Api/Client/BackupTransformer.php index 298c996427..6797c40ddb 100644 --- a/app/Transformers/Api/Client/BackupTransformer.php +++ b/app/Transformers/Api/Client/BackupTransformer.php @@ -3,26 +3,27 @@ namespace Pterodactyl\Transformers\Api\Client; use Pterodactyl\Models\Backup; +use Pterodactyl\Transformers\Api\Transformer; -class BackupTransformer extends BaseClientTransformer +class BackupTransformer extends Transformer { public function getResourceName(): string { return Backup::RESOURCE_NAME; } - public function transform(Backup $backup): array + public function transform(Backup $model): array { 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, - 'bytes' => $backup->bytes, - 'created_at' => $backup->created_at->toAtomString(), - 'completed_at' => $backup->completed_at ? $backup->completed_at->toAtomString() : null, + 'uuid' => $model->uuid, + 'is_successful' => $model->is_successful, + 'is_locked' => $model->is_locked, + 'name' => $model->name, + 'ignored_files' => $model->ignored_files, + 'checksum' => $model->checksum, + 'bytes' => $model->bytes, + 'created_at' => self::formatTimestamp($model->created_at), + 'completed_at' => self::formatTimestamp($model->completed_at), ]; } } diff --git a/app/Transformers/Api/Client/BaseClientTransformer.php b/app/Transformers/Api/Client/BaseClientTransformer.php deleted file mode 100644 index 0388effb08..0000000000 --- a/app/Transformers/Api/Client/BaseClientTransformer.php +++ /dev/null @@ -1,43 +0,0 @@ -request->user(); - } - - /** - * Determine if the API key loaded onto the transformer has permission - * to access a different resource. This is used when including other - * models on a transformation request. - * - * @noinspection PhpParameterNameChangedDuringInheritanceInspection - */ - protected function authorize(string $ability, Server $server = null): bool - { - Assert::isInstanceOf($server, Server::class); - - return $this->request->user()->can($ability, [$server]); - } - - /** - * {@inheritDoc} - */ - protected function makeTransformer(string $abstract) - { - Assert::subclassOf($abstract, self::class); - - return parent::makeTransformer($abstract); - } -} diff --git a/app/Transformers/Api/Client/DatabaseTransformer.php b/app/Transformers/Api/Client/DatabaseTransformer.php index 23e9666377..5ad899ad94 100644 --- a/app/Transformers/Api/Client/DatabaseTransformer.php +++ b/app/Transformers/Api/Client/DatabaseTransformer.php @@ -6,15 +6,15 @@ use League\Fractal\Resource\Item; use Pterodactyl\Models\Permission; use League\Fractal\Resource\NullResource; +use Pterodactyl\Transformers\Api\Transformer; use Illuminate\Contracts\Encryption\Encrypter; use Pterodactyl\Contracts\Extensions\HashidsInterface; -class DatabaseTransformer extends BaseClientTransformer +class DatabaseTransformer extends Transformer { protected array $availableIncludes = ['password']; private Encrypter $encrypter; - private HashidsInterface $hashids; /** @@ -38,8 +38,8 @@ public function transform(Database $model): array return [ 'id' => $this->hashids->encode($model->id), 'host' => [ - 'address' => $model->getRelation('host')->host, - 'port' => $model->getRelation('host')->port, + 'address' => $model->host->host, + 'port' => $model->host->port, ], 'name' => $model->database, 'username' => $model->username, @@ -53,7 +53,7 @@ public function transform(Database $model): array */ public function includePassword(Database $database): Item|NullResource { - if (!$this->request->user()->can(Permission::ACTION_DATABASE_VIEW_PASSWORD, $database->server)) { + if ($this->user()->cannot(Permission::ACTION_DATABASE_VIEW_PASSWORD, $database->server)) { return $this->null(); } diff --git a/app/Transformers/Api/Client/EggTransformer.php b/app/Transformers/Api/Client/EggTransformer.php index 8e2e3474e3..493958da59 100644 --- a/app/Transformers/Api/Client/EggTransformer.php +++ b/app/Transformers/Api/Client/EggTransformer.php @@ -3,8 +3,9 @@ namespace Pterodactyl\Transformers\Api\Client; use Pterodactyl\Models\Egg; +use Pterodactyl\Transformers\Api\Transformer; -class EggTransformer extends BaseClientTransformer +class EggTransformer extends Transformer { /** * Return the resource name for the JSONAPI output. @@ -14,11 +15,11 @@ public function getResourceName(): string return Egg::RESOURCE_NAME; } - public function transform(Egg $egg): array + public function transform(Egg $model): array { return [ - 'uuid' => $egg->uuid, - 'name' => $egg->name, + 'uuid' => $model->uuid, + 'name' => $model->name, ]; } } diff --git a/app/Transformers/Api/Client/EggVariableTransformer.php b/app/Transformers/Api/Client/EggVariableTransformer.php index 09c344249b..ea0ead081a 100644 --- a/app/Transformers/Api/Client/EggVariableTransformer.php +++ b/app/Transformers/Api/Client/EggVariableTransformer.php @@ -3,31 +3,32 @@ namespace Pterodactyl\Transformers\Api\Client; use Pterodactyl\Models\EggVariable; +use Pterodactyl\Transformers\Api\Transformer; -class EggVariableTransformer extends BaseClientTransformer +class EggVariableTransformer extends Transformer { public function getResourceName(): string { return EggVariable::RESOURCE_NAME; } - public function transform(EggVariable $variable): array + public function transform(EggVariable $model): array { // This guards against someone incorrectly retrieving variables (haha, me) and then passing // them into the transformer and along to the user. Just throw an exception and break the entire // pathway since you should never be exposing these types of variables to a client. - if (!$variable->user_viewable) { + if (!$model->user_viewable) { throw new \BadMethodCallException('Cannot transform a hidden egg variable in a client transformer.'); } return [ - 'name' => $variable->name, - 'description' => $variable->description, - 'env_variable' => $variable->env_variable, - 'default_value' => $variable->default_value, - 'server_value' => $variable->server_value, - 'is_editable' => $variable->user_editable, - 'rules' => $variable->rules, + 'name' => $model->name, + 'description' => $model->description, + 'env_variable' => $model->env_variable, + 'default_value' => $model->default_value, + 'server_value' => $model->server_value, + 'is_editable' => $model->user_editable, + 'rules' => $model->rules, ]; } } diff --git a/app/Transformers/Api/Client/FileObjectTransformer.php b/app/Transformers/Api/Client/FileObjectTransformer.php index 6278dad735..94a966000e 100644 --- a/app/Transformers/Api/Client/FileObjectTransformer.php +++ b/app/Transformers/Api/Client/FileObjectTransformer.php @@ -4,29 +4,30 @@ use Carbon\Carbon; use Illuminate\Support\Arr; +use Pterodactyl\Transformers\Api\Transformer; -class FileObjectTransformer extends BaseClientTransformer +class FileObjectTransformer extends Transformer { + public function getResourceName(): string + { + return 'file_object'; + } + /** * Transform a file object response from the daemon into a standardized response. */ - public function transform(array $item): array + public function transform(array $model): array { return [ - 'name' => Arr::get($item, 'name'), - 'mode' => Arr::get($item, 'mode'), - 'mode_bits' => Arr::get($item, 'mode_bits'), - 'size' => Arr::get($item, 'size'), - 'is_file' => Arr::get($item, 'file', true), - 'is_symlink' => Arr::get($item, 'symlink', false), - 'mimetype' => Arr::get($item, 'mime', 'application/octet-stream'), - 'created_at' => Carbon::parse(Arr::get($item, 'created', ''))->toAtomString(), - 'modified_at' => Carbon::parse(Arr::get($item, 'modified', ''))->toAtomString(), + 'name' => Arr::get($model, 'name'), + 'mode' => Arr::get($model, 'mode'), + 'mode_bits' => Arr::get($model, 'mode_bits'), + 'size' => Arr::get($model, 'size'), + 'is_file' => Arr::get($model, 'file', true), + 'is_symlink' => Arr::get($model, 'symlink', false), + 'mimetype' => Arr::get($model, 'mime', 'application/octet-stream'), + 'created_at' => Carbon::parse(Arr::get($model, 'created', ''))->toAtomString(), + 'modified_at' => Carbon::parse(Arr::get($model, 'modified', ''))->toAtomString(), ]; } - - public function getResourceName(): string - { - return 'file_object'; - } } diff --git a/app/Transformers/Api/Client/ScheduleTransformer.php b/app/Transformers/Api/Client/ScheduleTransformer.php index 98c783f45a..735e8fdd85 100644 --- a/app/Transformers/Api/Client/ScheduleTransformer.php +++ b/app/Transformers/Api/Client/ScheduleTransformer.php @@ -2,11 +2,11 @@ namespace Pterodactyl\Transformers\Api\Client; -use Pterodactyl\Models\Task; use Pterodactyl\Models\Schedule; use League\Fractal\Resource\Collection; +use Pterodactyl\Transformers\Api\Transformer; -class ScheduleTransformer extends BaseClientTransformer +class ScheduleTransformer extends Transformer { protected array $availableIncludes = ['tasks']; @@ -38,24 +38,18 @@ public function transform(Schedule $model): array 'is_active' => $model->is_active, 'is_processing' => $model->is_processing, 'only_when_online' => $model->only_when_online, - 'last_run_at' => $model->last_run_at?->toAtomString(), - 'next_run_at' => $model->next_run_at?->toAtomString(), - 'created_at' => $model->created_at->toAtomString(), - 'updated_at' => $model->updated_at->toAtomString(), + 'last_run_at' => self::formatTimestamp($model->last_run_at), + 'next_run_at' => self::formatTimestamp($model->next_run_at), + 'created_at' => self::formatTimestamp($model->created_at), + 'updated_at' => self::formatTimestamp($model->updated_at), ]; } /** * Allows attaching the tasks specific to the schedule in the response. - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function includeTasks(Schedule $model): Collection { - return $this->collection( - $model->tasks, - $this->makeTransformer(TaskTransformer::class), - Task::RESOURCE_NAME - ); + return $this->collection($model->tasks, new TaskTransformer()); } } diff --git a/app/Transformers/Api/Client/ServerTransformer.php b/app/Transformers/Api/Client/ServerTransformer.php index 9f7bce958a..782e6b4355 100644 --- a/app/Transformers/Api/Client/ServerTransformer.php +++ b/app/Transformers/Api/Client/ServerTransformer.php @@ -12,9 +12,10 @@ use Pterodactyl\Models\EggVariable; use League\Fractal\Resource\Collection; use League\Fractal\Resource\NullResource; +use Pterodactyl\Transformers\Api\Transformer; use Pterodactyl\Services\Servers\StartupCommandService; -class ServerTransformer extends BaseClientTransformer +class ServerTransformer extends Transformer { protected array $defaultIncludes = ['allocations', 'variables']; @@ -67,22 +68,16 @@ public function transform(Server $server): array 'backups' => $server->backup_limit, ], 'status' => $server->status, - // This field is deprecated, please use "status". - 'is_suspended' => $server->isSuspended(), - // This field is deprecated, please use "status". - 'is_installing' => !$server->isInstalled(), 'is_transferring' => !is_null($server->transfer), ]; } /** * Returns the allocations associated with this server. - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function includeAllocations(Server $server): Collection { - $transformer = $this->makeTransformer(AllocationTransformer::class); + $transformer = new AllocationTransformer(); $user = $this->request->user(); // While we include this permission, we do need to actually handle it slightly different here @@ -96,42 +91,31 @@ public function includeAllocations(Server $server): Collection $primary = clone $server->allocation; $primary->notes = null; - return $this->collection([$primary], $transformer, Allocation::RESOURCE_NAME); + return $this->collection([$primary], $transformer); } - return $this->collection($server->allocations, $transformer, Allocation::RESOURCE_NAME); + return $this->collection($server->allocations, $transformer); } - /** - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException - */ public function includeVariables(Server $server): Collection|NullResource { if (!$this->request->user()->can(Permission::ACTION_STARTUP_READ, $server)) { return $this->null(); } - return $this->collection( - $server->variables->where('user_viewable', true), - $this->makeTransformer(EggVariableTransformer::class), - EggVariable::RESOURCE_NAME - ); + return $this->collection($server->variables->where('user_viewable', true), new EggVariableTransformer()); } /** * Returns the egg associated with this server. - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function includeEgg(Server $server): Item { - return $this->item($server->egg, $this->makeTransformer(EggTransformer::class), Egg::RESOURCE_NAME); + return $this->item($server->egg, new EggTransformer()); } /** * Returns the subusers associated with this server. - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function includeSubusers(Server $server): Collection|NullResource { @@ -139,6 +123,6 @@ public function includeSubusers(Server $server): Collection|NullResource return $this->null(); } - return $this->collection($server->subusers, $this->makeTransformer(SubuserTransformer::class), Subuser::RESOURCE_NAME); + return $this->collection($server->subusers, new SubuserTransformer()); } } diff --git a/app/Transformers/Api/Client/StatsTransformer.php b/app/Transformers/Api/Client/StatsTransformer.php index 6b323b3154..0ae696cc3f 100644 --- a/app/Transformers/Api/Client/StatsTransformer.php +++ b/app/Transformers/Api/Client/StatsTransformer.php @@ -3,8 +3,9 @@ namespace Pterodactyl\Transformers\Api\Client; use Illuminate\Support\Arr; +use Pterodactyl\Transformers\Api\Transformer; -class StatsTransformer extends BaseClientTransformer +class StatsTransformer extends Transformer { public function getResourceName(): string { @@ -15,18 +16,18 @@ public function getResourceName(): string * Transform stats from the daemon into a result set that can be used in * the client API. */ - public function transform(array $data): array + public function transform(array $model): array { return [ - 'current_state' => Arr::get($data, 'state', 'stopped'), - 'is_suspended' => Arr::get($data, 'is_suspended', false), + 'current_state' => Arr::get($model, 'state', 'stopped'), + 'is_suspended' => Arr::get($model, 'is_suspended', false), 'resources' => [ - '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), - 'uptime' => Arr::get($data, 'utilization.uptime', 0), + 'memory_bytes' => Arr::get($model, 'utilization.memory_bytes', 0), + 'cpu_absolute' => Arr::get($model, 'utilization.cpu_absolute', 0), + 'disk_bytes' => Arr::get($model, 'utilization.disk_bytes', 0), + 'network_rx_bytes' => Arr::get($model, 'utilization.network.rx_bytes', 0), + 'network_tx_bytes' => Arr::get($model, 'utilization.network.tx_bytes', 0), + 'uptime' => Arr::get($model, 'utilization.uptime', 0), ], ]; } diff --git a/app/Transformers/Api/Client/SubuserTransformer.php b/app/Transformers/Api/Client/SubuserTransformer.php index 2902d1f7a2..4d26ae6555 100644 --- a/app/Transformers/Api/Client/SubuserTransformer.php +++ b/app/Transformers/Api/Client/SubuserTransformer.php @@ -3,8 +3,9 @@ namespace Pterodactyl\Transformers\Api\Client; use Pterodactyl\Models\Subuser; +use Pterodactyl\Transformers\Api\Transformer; -class SubuserTransformer extends BaseClientTransformer +class SubuserTransformer extends Transformer { /** * Return the resource name for the JSONAPI output. @@ -16,13 +17,11 @@ public function getResourceName(): string /** * Transforms a subuser into a model that can be shown to a front-end user. - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function transform(Subuser $model): array { return array_merge( - $this->makeTransformer(UserTransformer::class)->transform($model->user), + (new UserTransformer())->transform($model->user), ['permissions' => $model->permissions] ); } diff --git a/app/Transformers/Api/Client/TaskTransformer.php b/app/Transformers/Api/Client/TaskTransformer.php index 52f0e2b5d1..84054651e4 100644 --- a/app/Transformers/Api/Client/TaskTransformer.php +++ b/app/Transformers/Api/Client/TaskTransformer.php @@ -3,8 +3,9 @@ namespace Pterodactyl\Transformers\Api\Client; use Pterodactyl\Models\Task; +use Pterodactyl\Transformers\Api\Transformer; -class TaskTransformer extends BaseClientTransformer +class TaskTransformer extends Transformer { /** * {@inheritdoc} @@ -27,8 +28,8 @@ public function transform(Task $model): array 'time_offset' => $model->time_offset, 'is_queued' => $model->is_queued, 'continue_on_failure' => $model->continue_on_failure, - 'created_at' => $model->created_at->toAtomString(), - 'updated_at' => $model->updated_at->toAtomString(), + 'created_at' => self::formatTimestamp($model->created_at), + 'updated_at' => self::formatTimestamp($model->updated_at), ]; } } diff --git a/app/Transformers/Api/Client/UserSSHKeyTransformer.php b/app/Transformers/Api/Client/UserSSHKeyTransformer.php index 015a017b69..a13370eb59 100644 --- a/app/Transformers/Api/Client/UserSSHKeyTransformer.php +++ b/app/Transformers/Api/Client/UserSSHKeyTransformer.php @@ -3,8 +3,9 @@ namespace Pterodactyl\Transformers\Api\Client; use Pterodactyl\Models\UserSSHKey; +use Pterodactyl\Transformers\Api\Transformer; -class UserSSHKeyTransformer extends BaseClientTransformer +class UserSSHKeyTransformer extends Transformer { public function getResourceName(): string { @@ -20,7 +21,7 @@ public function transform(UserSSHKey $model): array 'name' => $model->name, 'fingerprint' => $model->fingerprint, 'public_key' => $model->public_key, - 'created_at' => $model->created_at->toAtomString(), + 'created_at' => self::formatTimestamp($model->created_at), ]; } } diff --git a/app/Transformers/Api/Client/UserTransformer.php b/app/Transformers/Api/Client/UserTransformer.php index 04bc70ea6d..b6275c3c10 100644 --- a/app/Transformers/Api/Client/UserTransformer.php +++ b/app/Transformers/Api/Client/UserTransformer.php @@ -4,8 +4,9 @@ use Illuminate\Support\Str; use Pterodactyl\Models\User; +use Pterodactyl\Transformers\Api\Transformer; -class UserTransformer extends BaseClientTransformer +class UserTransformer extends Transformer { /** * Return the resource name for the JSONAPI output. @@ -25,9 +26,9 @@ public function transform(User $model): array 'uuid' => $model->uuid, 'username' => $model->username, 'email' => $model->email, - 'image' => 'https://gravatar.com/avatar/' . md5(Str::lower($model->email)), + 'image' => $model->avatarURL(), '2fa_enabled' => $model->use_totp, - 'created_at' => $model->created_at->toAtomString(), + 'created_at' => self::formatTimestamp($model->created_at), ]; } } diff --git a/app/Transformers/Api/Transformer.php b/app/Transformers/Api/Transformer.php new file mode 100644 index 0000000000..8b7e013bba --- /dev/null +++ b/app/Transformers/Api/Transformer.php @@ -0,0 +1,156 @@ +request = Container::getInstance()->make('request'); + + if (method_exists($this, 'handle')) { + Container::getInstance()->call([$this, 'handle']); + } + } + + /** + * Returns the resource name for the transformed item. + */ + abstract public function getResourceName(): string; + + /** + * Returns the authorized user for the request. + */ + protected function user(): User + { + return $this->request->user(); + } + + /** + * Determines if the user making this request is authorized to access the given + * resource on the API. This is used when requested included items to ensure that + * the user and key are authorized to see the result. + * + * TODO: implement this with the new API key formats. + */ + protected function authorize(string $resource): bool + { + return $this->request->user() instanceof User; + } + + /** + * {@inheritDoc} + * + * @param mixed $data + * @param callable|\League\Fractal\TransformerAbstract $transformer + */ + protected function item($data, $transformer, ?string $resourceKey = null): Item + { + if (!$transformer instanceof Closure) { + self::assertSameNamespace($transformer); + } + + $item = parent::item($data, $transformer, $resourceKey); + + if (!$item->getResourceKey() && method_exists($transformer, 'getResourceName')) { + $item->setResourceKey($transformer->getResourceName()); + } + + return $item; + } + + /** + * {@inheritDoc} + * + * @param mixed $data + * @param callable|\League\Fractal\TransformerAbstract $transformer + */ + protected function collection($data, $transformer, ?string $resourceKey = null): Collection + { + if (!$transformer instanceof Closure) { + self::assertSameNamespace($transformer); + } + + $collection = parent::collection($data, $transformer, $resourceKey); + + if (!$collection->getResourceKey() && method_exists($transformer, 'getResourceName')) { + $collection->setResourceKey($transformer->getResourceName()); + } + + return $collection; + } + + /** + * Sets the default timezone to use for transformed responses. Pass a null value + * to return back to the default timezone (UTC). + */ + public static function setTimezone(string $tz = null) + { + static::$timezone = $tz ?? 'UTC'; + } + + /** + * Asserts that the given transformer is the same base namespace as the class that + * implements this abstract transformer class. This prevents a client or application + * transformer from unintentionally transforming a resource using an unexpected type. + * + * @param callable|\League\Fractal\TransformerAbstract $transformer + */ + protected static function assertSameNamespace($transformer) + { + Assert::subclassOf($transformer, TransformerAbstract::class); + + $namespace = substr(get_class($transformer), 0, strlen(class_basename($transformer)) * -1); + $expected = substr(static::class, 0, strlen(class_basename(static::class)) * -1); + + Assert::same($namespace, $expected, 'Cannot invoke a new transformer (%s) that is not in the same namespace (%s).'); + } + + /** + * Returns an ISO-8601 formatted timestamp to use in API responses. This + * time is returned in the default transformer timezone if no timezone value + * is provided. + * + * If no time is provided a null value is returned. + * + * @param string|\DateTimeInterface|null $timestamp + */ + protected static function formatTimestamp($timestamp, string $tz = null): ?string + { + if (empty($timestamp)) { + return null; + } + + if ($timestamp instanceof DateTimeInterface) { + $value = CarbonImmutable::instance($timestamp); + } else { + $value = CarbonImmutable::createFromFormat(CarbonInterface::DEFAULT_TO_STRING_FORMAT, $timestamp); + } + + return $value->setTimezone($tz ?? self::$timezone)->toAtomString(); + } +} diff --git a/database/Seeders/EggSeeder.php b/database/Seeders/EggSeeder.php index 234e7b5a43..523494714e 100644 --- a/database/Seeders/EggSeeder.php +++ b/database/Seeders/EggSeeder.php @@ -11,10 +11,6 @@ class EggSeeder extends Seeder { - protected EggImporterService $importerService; - - protected EggUpdateImporterService $updateImporterService; - /** * @var string[] */ @@ -29,15 +25,15 @@ class EggSeeder extends Seeder * EggSeeder constructor. */ public function __construct( - EggImporterService $importerService, - EggUpdateImporterService $updateImporterService + private EggImporterService $importerService, + private EggUpdateImporterService $updateImporterService ) { - $this->importerService = $importerService; - $this->updateImporterService = $updateImporterService; } /** * Run the egg seeder. + * + * @throws \JsonException */ public function run() { @@ -51,6 +47,8 @@ public function run() /** * Loop through the list of egg files and import them. + * + * @throws \JsonException */ protected function parseEggFiles(Nest $nest) { @@ -75,7 +73,7 @@ protected function parseEggFiles(Nest $nest) $this->updateImporterService->handle($egg, $file); $this->command->info('Updated ' . $decoded['name']); } else { - $this->importerService->handle($file, $nest->id); + $this->importerService->handleFile($nest->id, $file); $this->command->comment('Created ' . $decoded['name']); } } diff --git a/database/migrations/2020_09_25_021109_create_admin_roles_table.php b/database/migrations/2020_09_25_021109_create_admin_roles_table.php new file mode 100644 index 0000000000..e67f750748 --- /dev/null +++ b/database/migrations/2020_09_25_021109_create_admin_roles_table.php @@ -0,0 +1,31 @@ +increments('id'); + $table->string('name', 64); + $table->string('description', 255)->nullable(); + $table->integer('sort_id'); + $table->json('permissions')->nullable(); + + $table->unique(['id']); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('admin_roles'); + } +}; diff --git a/database/migrations/2021_01_16_201057_add_admin_role_id_column_to_users_table.php b/database/migrations/2021_01_16_201057_add_admin_role_id_column_to_users_table.php new file mode 100644 index 0000000000..641dc62dbe --- /dev/null +++ b/database/migrations/2021_01_16_201057_add_admin_role_id_column_to_users_table.php @@ -0,0 +1,30 @@ +integer('admin_role_id')->nullable()->unsigned()->after('language'); + $table->index('admin_role_id'); + $table->foreign('admin_role_id')->references('id')->on('admin_roles')->onDelete('set null'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('users', function (Blueprint $table) { + $table->dropForeign(['admin_role_id']); + $table->dropColumn('admin_role_id'); + }); + } +}; diff --git a/database/migrations/2021_01_30_193343_add_database_host_id_column_to_nodes_table.php b/database/migrations/2021_01_30_193343_add_database_host_id_column_to_nodes_table.php new file mode 100644 index 0000000000..e45ad76189 --- /dev/null +++ b/database/migrations/2021_01_30_193343_add_database_host_id_column_to_nodes_table.php @@ -0,0 +1,41 @@ +dropForeign(['node_id']); + $table->dropColumn('node_id'); + }); + + Schema::table('nodes', function (Blueprint $table) { + $table->integer('database_host_id')->nullable()->unsigned()->after('location_id'); + $table->index('database_host_id')->nullable(); + $table->foreign('database_host_id')->references('id')->on('database_hosts')->onDelete('set null'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('nodes', function (Blueprint $table) { + $table->dropForeign(['database_host_id']); + $table->dropColumn('database_host_id'); + }); + + Schema::table('database_hosts', function (Blueprint $table) { + $table->integer('node_id')->nullable()->unsigned()->after('max_databases'); + $table->index('node_id')->nullable(); + $table->foreign('node_id')->references('id')->on('nodes'); + }); + } +}; diff --git a/database/migrations/2021_02_23_212658_change_port_columns_on_nodes_table.php b/database/migrations/2021_02_23_212658_change_port_columns_on_nodes_table.php new file mode 100644 index 0000000000..2e8010c9b6 --- /dev/null +++ b/database/migrations/2021_02_23_212658_change_port_columns_on_nodes_table.php @@ -0,0 +1,58 @@ +renameColumn('daemonListen', 'listen_port_http'); + $table->renameColumn('daemonSFTP', 'listen_port_sftp'); + $table->renameColumn('daemonBase', 'daemon_base'); + }); + + Schema::table('nodes', function (Blueprint $table) { + $table->integer('listen_port_http')->unsigned()->default(8080)->after('fqdn')->change(); + $table->integer('listen_port_sftp')->unsigned()->default(2022)->after('listen_port_sftp')->change(); + + $table->integer('public_port_http')->unsigned()->default(8080)->after('listen_port_http'); + $table->integer('public_port_sftp')->unsigned()->default(2022)->after('listen_port_sftp'); + }); + + DB::transaction(function () { + foreach (DB::select('SELECT id, listen_port_http, listen_port_sftp FROM nodes') as $datum) { + DB::update('UPDATE nodes SET public_port_http = ?, public_port_sftp = ? WHERE id = ?', [ + $datum->listen_port_http, + $datum->listen_port_sftp, + $datum->id, + ]); + } + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('nodes', function (Blueprint $table) { + $table->renameColumn('listen_port_http', 'daemonListen'); + $table->renameColumn('listen_port_sftp', 'daemonSFTP'); + $table->renameColumn('daemon_base', 'daemonBase'); + + $table->dropColumn('public_port_http'); + $table->dropColumn('public_port_sftp'); + }); + + Schema::table('nodes', function (Blueprint $table) { + $table->smallInteger('daemonListen')->unsigned()->default(8080)->after('daemon_token')->change(); + $table->smallInteger('daemonSFTP')->unsigned()->default(2022)->after('daemonListen')->change(); + }); + } +}; diff --git a/database/migrations/2021_07_29_032255_yeet_names_from_users_table.php b/database/migrations/2021_07_29_032255_yeet_names_from_users_table.php new file mode 100644 index 0000000000..0a22a2059e --- /dev/null +++ b/database/migrations/2021_07_29_032255_yeet_names_from_users_table.php @@ -0,0 +1,28 @@ +dropColumn(['name_first', 'name_last']); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('users', function (Blueprint $table) { + $table->string('name_first')->after('email')->nullable(); + $table->string('name_last')->after('name_first')->nullable(); + }); + } +}; diff --git a/database/migrations/2021_10_23_185304_drop_config_logs_column_from_eggs_table.php b/database/migrations/2021_10_23_185304_drop_config_logs_column_from_eggs_table.php new file mode 100644 index 0000000000..b78a9698c7 --- /dev/null +++ b/database/migrations/2021_10_23_185304_drop_config_logs_column_from_eggs_table.php @@ -0,0 +1,27 @@ +dropColumn('config_logs'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('eggs', function (Blueprint $table) { + $table->text('config_logs')->nullable()->after('docker_image'); + }); + } +}; diff --git a/database/migrations/2021_10_23_202643_update_default_values_for_eggs.php b/database/migrations/2021_10_23_202643_update_default_values_for_eggs.php new file mode 100644 index 0000000000..fee18b92bb --- /dev/null +++ b/database/migrations/2021_10_23_202643_update_default_values_for_eggs.php @@ -0,0 +1,28 @@ +string('script_container')->default('ghcr.io/pterodactyl/installers:alpine')->after('startup')->change(); + $table->string('script_entry')->default('/bin/ash')->after('copy_script_from')->change(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('eggs', function (Blueprint $table) { + // You are stuck with the new values because I am too lazy to revert them :) + }); + } +}; diff --git a/database/migrations/2021_11_01_180130_make_startup_field_nullable_on_servers_table.php b/database/migrations/2021_11_01_180130_make_startup_field_nullable_on_servers_table.php new file mode 100644 index 0000000000..3795eb7528 --- /dev/null +++ b/database/migrations/2021_11_01_180130_make_startup_field_nullable_on_servers_table.php @@ -0,0 +1,27 @@ +text('startup')->default(null)->nullable()->change(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('servers', function (Blueprint $table) { + $table->text('startup')->change(); + }); + } +}; diff --git a/resources/views/templates/wrapper.blade.php b/resources/views/templates/wrapper.blade.php index 8624d2e853..dd3ed4c5dd 100644 --- a/resources/views/templates/wrapper.blade.php +++ b/resources/views/templates/wrapper.blade.php @@ -22,7 +22,7 @@ @section('user-data') @if(!is_null(Auth::user())) @endif @if(!empty($siteConfiguration)) diff --git a/routes/api-application.php b/routes/api-application.php index dc6b0e5bb6..cedbcd1d02 100644 --- a/routes/api-application.php +++ b/routes/api-application.php @@ -3,24 +3,111 @@ use Illuminate\Support\Facades\Route; use Pterodactyl\Http\Controllers\Api\Application; +Route::get('/version', [Application\VersionController::class]); + /* |-------------------------------------------------------------------------- -| User Controller Routes +| Database Controller Routes |-------------------------------------------------------------------------- | -| Endpoint: /api/application/users +| Endpoint: /api/application/databases | */ +Route::group(['prefix' => '/databases'], function () { + Route::get('/', [Application\Databases\DatabaseController::class, 'index']); + Route::get('/{databaseHost}', [Application\Databases\DatabaseController::class, 'view']); -Route::group(['prefix' => '/users'], function () { - Route::get('/', [Application\Users\UserController::class, 'index'])->name('api.application.users'); - Route::get('/{user:id}', [Application\Users\UserController::class, 'view'])->name('api.application.users.view'); - Route::get('/external/{external_id}', [Application\Users\ExternalUserController::class, 'index'])->name('api.application.users.external'); + Route::post('/', [Application\Databases\DatabaseController::class, 'store']); - Route::post('/', [Application\Users\UserController::class, 'store']); - Route::patch('/{user:id}', [Application\Users\UserController::class, 'update']); + Route::patch('/{databaseHost}', [Application\Databases\DatabaseController::class, 'update']); - Route::delete('/{user:id}', [Application\Users\UserController::class, 'delete']); + Route::delete('/{databaseHost}', [Application\Databases\DatabaseController::class, 'delete']); +}); + +/* +|-------------------------------------------------------------------------- +| Egg Controller Routes +|-------------------------------------------------------------------------- +| +| Endpoint: /api/application/eggs +| +*/ +Route::group(['prefix' => '/eggs'], function () { + Route::get('/{egg}', [Application\Eggs\EggController::class, 'view']); + Route::get('/{egg}/export', [Application\Eggs\EggController::class, 'export']); + + Route::post('/', [Application\Eggs\EggController::class, 'store']); + Route::post('/{egg}/variables', [Application\Eggs\EggVariableController::class, 'store']); + + Route::patch('/{egg}', [Application\Eggs\EggController::class, 'update']); + Route::patch('/{egg}/variables', [Application\Eggs\EggVariableController::class, 'update']); + + Route::delete('/{egg}', [Application\Eggs\EggController::class, 'delete']); + Route::delete('/{egg}/variables/{eggVariable}', [Application\Eggs\EggVariableController::class, 'delete']); +}); + +/* +|-------------------------------------------------------------------------- +| Location Controller Routes +|-------------------------------------------------------------------------- +| +| Endpoint: /api/application/locations +| +*/ +Route::group(['prefix' => '/locations'], function () { + Route::get('/', [Application\Locations\LocationController::class, 'index']); + Route::get('/{location}', [Application\Locations\LocationController::class, 'view']); + + Route::post('/', [Application\Locations\LocationController::class, 'store']); + + Route::patch('/{location}', [Application\Locations\LocationController::class, 'update']); + + Route::delete('/{location}', [Application\Locations\LocationController::class, 'delete']); +}); + +/* +|-------------------------------------------------------------------------- +| Mount Controller Routes +|-------------------------------------------------------------------------- +| +| Endpoint: /api/application/mounts +| +*/ +Route::group(['prefix' => '/mounts'], function () { + Route::get('/', [Application\Mounts\MountController::class, 'index']); + Route::get('/{mount}', [Application\Mounts\MountController::class, 'view']); + + Route::post('/', [Application\Mounts\MountController::class, 'store']); + + Route::put('/{mount}/eggs', [Application\Mounts\MountController::class, 'addEggs']); + Route::put('/{mount}/nodes', [Application\Mounts\MountController::class, 'addNodes']); + + Route::patch('/{mount}', [Application\Mounts\MountController::class, 'update']); + + Route::delete('/{mount}', [Application\Mounts\MountController::class, 'delete']); + Route::delete('/{mount}/eggs', [Application\Mounts\MountController::class, 'deleteEggs']); + Route::delete('/{mount}/nodes', [Application\Mounts\MountController::class, 'deleteNodes']); +}); + +/* +|-------------------------------------------------------------------------- +| Nest Controller Routes +|-------------------------------------------------------------------------- +| +| Endpoint: /api/application/nests +| +*/ +Route::group(['prefix' => '/nests'], function () { + Route::get('/', [Application\Nests\NestController::class, 'index']); + Route::get('/{nest}', [Application\Nests\NestController::class, 'view']); + Route::get('/{nest}/eggs', [Application\Eggs\EggController::class, 'index']); + + Route::post('/', [Application\Nests\NestController::class, 'store']); + Route::post('/{nest}/import', [Application\Nests\NestController::class, 'import']); + + Route::patch('/{nest}', [Application\Nests\NestController::class, 'update']); + + Route::delete('/{nest}', [Application\Nests\NestController::class, 'delete']); }); /* @@ -32,39 +119,42 @@ | */ Route::group(['prefix' => '/nodes'], function () { - Route::get('/', [Application\Nodes\NodeController::class, 'index'])->name('api.application.nodes'); - Route::get('/deployable', Application\Nodes\NodeDeploymentController::class); - Route::get('/{node:id}', [Application\Nodes\NodeController::class, 'view'])->name('api.application.nodes.view'); - Route::get('/{node:id}/configuration', Application\Nodes\NodeConfigurationController::class); + Route::get('/', [Application\Nodes\NodeController::class, 'index']); + Route::get('/deployable', [Application\Nodes\NodeDeploymentController::class, '__invoke']); + Route::get('/{node}', [Application\Nodes\NodeController::class, 'view']); + Route::get('/{node}/configuration', [Application\Nodes\NodeConfigurationController::class, '__invoke']); + Route::get('/{node}/information', [Application\Nodes\NodeInformationController::class, '__invoke']); Route::post('/', [Application\Nodes\NodeController::class, 'store']); - Route::patch('/{node:id}', [Application\Nodes\NodeController::class, 'update']); - Route::delete('/{node:id}', [Application\Nodes\NodeController::class, 'delete']); + Route::patch('/{node}', [Application\Nodes\NodeController::class, 'update']); + + Route::delete('/{node}', [Application\Nodes\NodeController::class, 'delete']); - Route::group(['prefix' => '/{node:id}/allocations'], function () { - Route::get('/', [Application\Nodes\AllocationController::class, 'index'])->name('api.application.allocations'); + Route::group(['prefix' => '/{node}/allocations'], function () { + Route::get('/', [Application\Nodes\AllocationController::class, 'index']); Route::post('/', [Application\Nodes\AllocationController::class, 'store']); - Route::delete('/{allocation:id}', [Application\Nodes\AllocationController::class, 'delete'])->name('api.application.allocations.view'); + Route::delete('/{allocation}', [Application\Nodes\AllocationController::class, 'delete']); }); }); /* |-------------------------------------------------------------------------- -| Location Controller Routes +| Role Controller Routes |-------------------------------------------------------------------------- | -| Endpoint: /api/application/locations +| Endpoint: /api/application/roles | */ -Route::group(['prefix' => '/locations'], function () { - Route::get('/', [Application\Locations\LocationController::class, 'index'])->name('api.applications.locations'); - Route::get('/{location:id}', [Application\Locations\LocationController::class, 'view'])->name('api.application.locations.view'); +Route::group(['prefix' => '/roles'], function () { + Route::get('/', [Application\Roles\RoleController::class, 'index']); + Route::get('/{role}', [Application\Roles\RoleController::class, 'view']); - Route::post('/', [Application\Locations\LocationController::class, 'store']); - Route::patch('/{location:id}', [Application\Locations\LocationController::class, 'update']); + Route::post('/', [Application\Roles\RoleController::class, 'store']); - Route::delete('/{location:id}', [Application\Locations\LocationController::class, 'delete']); + Route::patch('/{role}', [Application\Roles\RoleController::class, 'update']); + + Route::delete('/{role}', [Application\Roles\RoleController::class, 'delete']); }); /* @@ -76,49 +166,49 @@ | */ Route::group(['prefix' => '/servers'], function () { - Route::get('/', [Application\Servers\ServerController::class, 'index'])->name('api.application.servers'); - Route::get('/{server:id}', [Application\Servers\ServerController::class, 'view'])->name('api.application.servers.view'); - Route::get('/external/{external_id}', [Application\Servers\ExternalServerController::class, 'index'])->name('api.application.servers.external'); + Route::get('/', [Application\Servers\ServerController::class, 'index']); + Route::get('/{server}', [Application\Servers\ServerController::class, 'view']); + Route::get('/external/{external_id}', [Application\Servers\ExternalServerController::class, 'index']); - Route::patch('/{server:id}/details', [Application\Servers\ServerDetailsController::class, 'details'])->name('api.application.servers.details'); - Route::patch('/{server:id}/build', [Application\Servers\ServerDetailsController::class, 'build'])->name('api.application.servers.build'); - Route::patch('/{server:id}/startup', [Application\Servers\StartupController::class, 'index'])->name('api.application.servers.startup'); + Route::patch('/{server}', [Application\Servers\ServerController::class, 'update']); + Route::patch('/{server}/startup', [Application\Servers\StartupController::class, 'index']); Route::post('/', [Application\Servers\ServerController::class, 'store']); - Route::post('/{server:id}/suspend', [Application\Servers\ServerManagementController::class, 'suspend'])->name('api.application.servers.suspend'); - Route::post('/{server:id}/unsuspend', [Application\Servers\ServerManagementController::class, 'unsuspend'])->name('api.application.servers.unsuspend'); - Route::post('/{server:id}/reinstall', [Application\Servers\ServerManagementController::class, 'reinstall'])->name('api.application.servers.reinstall'); + Route::post('/{server}/suspend', [Application\Servers\ServerManagementController::class, 'suspend']); + Route::post('/{server}/unsuspend', [Application\Servers\ServerManagementController::class, 'unsuspend']); + Route::post('/{server}/reinstall', [Application\Servers\ServerManagementController::class, 'reinstall']); - Route::delete('/{server:id}', [Application\Servers\ServerController::class, 'delete']); - Route::delete('/{server:id}/{force?}', [Application\Servers\ServerController::class, 'delete']); + Route::delete('/{server}', [Application\Servers\ServerController::class, 'delete']); + Route::delete('/{server}/{force?}', [Application\Servers\ServerController::class, 'delete']); // Database Management Endpoint - Route::group(['prefix' => '/{server:id}/databases'], function () { - Route::get('/', [Application\Servers\DatabaseController::class, 'index'])->name('api.application.servers.databases'); - Route::get('/{database:id}', [Application\Servers\DatabaseController::class, 'view'])->name('api.application.servers.databases.view'); + Route::group(['prefix' => '/{server}/databases'], function () { + Route::get('/', [Application\Servers\DatabaseController::class, 'index']); + Route::get('/{database}', [Application\Servers\DatabaseController::class, 'view']); Route::post('/', [Application\Servers\DatabaseController::class, 'store']); - Route::post('/{database:id}/reset-password', [Application\Servers\DatabaseController::class, 'resetPassword']); + Route::post('/{database}/reset-password', [Application\Servers\DatabaseController::class, 'resetPassword']); - Route::delete('/{database:id}', [Application\Servers\DatabaseController::class, 'delete']); + Route::delete('/{database}', [Application\Servers\DatabaseController::class, 'delete']); }); }); /* |-------------------------------------------------------------------------- -| Nest Controller Routes +| User Controller Routes |-------------------------------------------------------------------------- | -| Endpoint: /api/application/nests +| Endpoint: /api/application/users | */ -Route::group(['prefix' => '/nests'], function () { - Route::get('/', [Application\Nests\NestController::class, 'index'])->name('api.application.nests'); - Route::get('/{nest:id}', [Application\Nests\NestController::class, 'view'])->name('api.application.nests.view'); +Route::group(['prefix' => '/users'], function () { + Route::get('/', [Application\Users\UserController::class, 'index']); + Route::get('/{user}', [Application\Users\UserController::class, 'view']); + Route::get('/external/{external_id}', [Application\Users\ExternalUserController::class, 'index']); - // Egg Management Endpoint - Route::group(['prefix' => '/{nest:id}/eggs'], function () { - Route::get('/', [Application\Nests\EggController::class, 'index'])->name('api.application.nests.eggs'); - Route::get('/{egg:id}', [Application\Nests\EggController::class, 'view'])->name('api.application.nests.eggs.view'); - }); + Route::post('/', [Application\Users\UserController::class, 'store']); + + Route::patch('/{user}', [Application\Users\UserController::class, 'update']); + + Route::delete('/{user}', [Application\Users\UserController::class, 'delete']); }); diff --git a/tests/Integration/Api/Application/ApplicationApiIntegrationTestCase.php b/tests/Integration/Api/Application/ApplicationApiIntegrationTestCase.php index 73a4f8f51a..53d902e23b 100644 --- a/tests/Integration/Api/Application/ApplicationApiIntegrationTestCase.php +++ b/tests/Integration/Api/Application/ApplicationApiIntegrationTestCase.php @@ -2,16 +2,13 @@ namespace Pterodactyl\Tests\Integration\Api\Application; -use Illuminate\Http\Request; use Pterodactyl\Models\User; -use PHPUnit\Framework\Assert; use Pterodactyl\Models\ApiKey; use Pterodactyl\Services\Acl\Api\AdminAcl; +use Pterodactyl\Transformers\Api\Transformer; use Pterodactyl\Tests\Integration\IntegrationTestCase; use Illuminate\Foundation\Testing\DatabaseTransactions; use Pterodactyl\Tests\Traits\Integration\CreatesTestModels; -use Pterodactyl\Transformers\Api\Application\BaseTransformer; -use Pterodactyl\Transformers\Api\Client\BaseClientTransformer; use Pterodactyl\Tests\Traits\Http\IntegrationJsonRequestAssertions; abstract class ApplicationApiIntegrationTestCase extends IntegrationTestCase @@ -95,18 +92,8 @@ protected function createApiKey(User $user, array $permissions = []): ApiKey /** * Return a transformer that can be used for testing purposes. */ - protected function getTransformer(string $abstract): BaseTransformer + protected function getTransformer(string $abstract): Transformer { - $request = Request::createFromGlobals(); - $request->setUserResolver(function () { - return $this->getApiKey()->user; - }); - - $transformer = $abstract::fromRequest($request); - - Assert::assertInstanceOf(BaseTransformer::class, $transformer); - Assert::assertNotInstanceOf(BaseClientTransformer::class, $transformer); - - return $transformer; + return new $abstract(); } } diff --git a/tests/Integration/IntegrationTestCase.php b/tests/Integration/IntegrationTestCase.php index 6672960d32..428defd71a 100644 --- a/tests/Integration/IntegrationTestCase.php +++ b/tests/Integration/IntegrationTestCase.php @@ -9,15 +9,12 @@ use Pterodactyl\Events\ActivityLogged; use Pterodactyl\Tests\Assertions\AssertsActivityLogged; use Pterodactyl\Tests\Traits\Integration\CreatesTestModels; -use Pterodactyl\Transformers\Api\Application\BaseTransformer; abstract class IntegrationTestCase extends TestCase { use CreatesTestModels; use AssertsActivityLogged; -// protected array $connectionsToTransact = ['pgsql']; - protected $defaultHeaders = [ 'Accept' => 'application/json', ]; @@ -35,7 +32,7 @@ public function setUp(): void protected function formatTimestamp(string $timestamp): string { return CarbonImmutable::createFromFormat(CarbonInterface::DEFAULT_TO_STRING_FORMAT, $timestamp) - ->setTimezone(BaseTransformer::RESPONSE_TIMEZONE) + ->setTimezone('UTC') ->toAtomString(); } } diff --git a/tests/Traits/Http/MocksMiddlewareClosure.php b/tests/Traits/Http/MocksMiddlewareClosure.php index 9cbe315bd3..3519238bb7 100644 --- a/tests/Traits/Http/MocksMiddlewareClosure.php +++ b/tests/Traits/Http/MocksMiddlewareClosure.php @@ -11,7 +11,7 @@ trait MocksMiddlewareClosure * Provide a closure to be used when validating that the response from the middleware * is the same request object we passed into it. */ - protected function getClosureAssertions(): \Closure + protected function getClosureAssertions(): Closure { if (is_null($this->request)) { throw new \BadFunctionCallException('Calling getClosureAssertions without defining a request object is not supported.'); From 363c4fd49f87de5acda9a32b1a69c041e5d2a128 Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Wed, 14 Dec 2022 17:06:28 -0700 Subject: [PATCH 334/458] php-cs-fixer --- app/Exceptions/Handler.php | 2 +- app/Http/Controllers/Api/Client/ClientApiController.php | 1 - app/Services/Eggs/Sharing/EggUpdateImporterService.php | 2 +- app/Transformers/Api/Client/ServerTransformer.php | 2 -- app/Transformers/Api/Client/UserTransformer.php | 1 - app/Transformers/Api/Transformer.php | 8 +++----- tests/Traits/Http/MocksMiddlewareClosure.php | 2 +- 7 files changed, 6 insertions(+), 12 deletions(-) diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index 4cfee8e461..6fd1b5d8e0 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -233,7 +233,7 @@ protected function convertExceptionToArray(\Throwable $e, array $override = []): /** * Return an array of exceptions that should not be reported. */ - public static function isReportable(Exception $exception): bool + public static function isReportable(\Exception $exception): bool { return (new static(Container::getInstance()))->shouldReport($exception); } diff --git a/app/Http/Controllers/Api/Client/ClientApiController.php b/app/Http/Controllers/Api/Client/ClientApiController.php index 380cbf5480..12a09cb04c 100644 --- a/app/Http/Controllers/Api/Client/ClientApiController.php +++ b/app/Http/Controllers/Api/Client/ClientApiController.php @@ -2,7 +2,6 @@ namespace Pterodactyl\Http\Controllers\Api\Client; -use Webmozart\Assert\Assert; use Pterodactyl\Transformers\Api\Transformer; use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController; diff --git a/app/Services/Eggs/Sharing/EggUpdateImporterService.php b/app/Services/Eggs/Sharing/EggUpdateImporterService.php index ad17a16f0a..d428ce8f40 100644 --- a/app/Services/Eggs/Sharing/EggUpdateImporterService.php +++ b/app/Services/Eggs/Sharing/EggUpdateImporterService.php @@ -8,8 +8,8 @@ use Pterodactyl\Models\EggVariable; use Illuminate\Database\ConnectionInterface; use Pterodactyl\Services\Eggs\EggParserService; -use Pterodactyl\Exceptions\Service\InvalidFileUploadException; use Pterodactyl\Exceptions\Service\Egg\BadJsonFormatException; +use Pterodactyl\Exceptions\Service\InvalidFileUploadException; class EggUpdateImporterService { diff --git a/app/Transformers/Api/Client/ServerTransformer.php b/app/Transformers/Api/Client/ServerTransformer.php index 782e6b4355..401c349052 100644 --- a/app/Transformers/Api/Client/ServerTransformer.php +++ b/app/Transformers/Api/Client/ServerTransformer.php @@ -4,12 +4,10 @@ use Pterodactyl\Models\Egg; use Pterodactyl\Models\Server; -use Pterodactyl\Models\Subuser; use League\Fractal\Resource\Item; use Pterodactyl\Models\Allocation; use Pterodactyl\Models\Permission; use Illuminate\Container\Container; -use Pterodactyl\Models\EggVariable; use League\Fractal\Resource\Collection; use League\Fractal\Resource\NullResource; use Pterodactyl\Transformers\Api\Transformer; diff --git a/app/Transformers/Api/Client/UserTransformer.php b/app/Transformers/Api/Client/UserTransformer.php index b6275c3c10..7a2f077546 100644 --- a/app/Transformers/Api/Client/UserTransformer.php +++ b/app/Transformers/Api/Client/UserTransformer.php @@ -2,7 +2,6 @@ namespace Pterodactyl\Transformers\Api\Client; -use Illuminate\Support\Str; use Pterodactyl\Models\User; use Pterodactyl\Transformers\Api\Transformer; diff --git a/app/Transformers/Api/Transformer.php b/app/Transformers/Api/Transformer.php index 8b7e013bba..89051d77fe 100644 --- a/app/Transformers/Api/Transformer.php +++ b/app/Transformers/Api/Transformer.php @@ -2,8 +2,6 @@ namespace Pterodactyl\Transformers\Api; -use Closure; -use DateTimeInterface; use Carbon\CarbonImmutable; use Carbon\CarbonInterface; use Illuminate\Http\Request; @@ -70,7 +68,7 @@ protected function authorize(string $resource): bool */ protected function item($data, $transformer, ?string $resourceKey = null): Item { - if (!$transformer instanceof Closure) { + if (!$transformer instanceof \Closure) { self::assertSameNamespace($transformer); } @@ -91,7 +89,7 @@ protected function item($data, $transformer, ?string $resourceKey = null): Item */ protected function collection($data, $transformer, ?string $resourceKey = null): Collection { - if (!$transformer instanceof Closure) { + if (!$transformer instanceof \Closure) { self::assertSameNamespace($transformer); } @@ -145,7 +143,7 @@ protected static function formatTimestamp($timestamp, string $tz = null): ?strin return null; } - if ($timestamp instanceof DateTimeInterface) { + if ($timestamp instanceof \DateTimeInterface) { $value = CarbonImmutable::instance($timestamp); } else { $value = CarbonImmutable::createFromFormat(CarbonInterface::DEFAULT_TO_STRING_FORMAT, $timestamp); diff --git a/tests/Traits/Http/MocksMiddlewareClosure.php b/tests/Traits/Http/MocksMiddlewareClosure.php index 3519238bb7..9cbe315bd3 100644 --- a/tests/Traits/Http/MocksMiddlewareClosure.php +++ b/tests/Traits/Http/MocksMiddlewareClosure.php @@ -11,7 +11,7 @@ trait MocksMiddlewareClosure * Provide a closure to be used when validating that the response from the middleware * is the same request object we passed into it. */ - protected function getClosureAssertions(): Closure + protected function getClosureAssertions(): \Closure { if (is_null($this->request)) { throw new \BadFunctionCallException('Calling getClosureAssertions without defining a request object is not supported.'); From 7ed2be50fdcc40b3ec305229a20393e028394108 Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Wed, 14 Dec 2022 18:04:16 -0700 Subject: [PATCH 335/458] php-cs-fixer and phpstan --- app/Console/Commands/InfoCommand.php | 8 +- .../Nodes/NodeDeploymentController.php | 2 +- .../Databases/UpdateDatabaseRequest.php | 2 +- .../Locations/UpdateLocationRequest.php | 2 +- .../Application/Mounts/UpdateMountRequest.php | 2 +- .../Application/Nests/UpdateNestRequest.php | 2 +- .../Application/Nodes/StoreNodeRequest.php | 11 +-- .../Application/Nodes/UpdateNodeRequest.php | 2 +- .../Application/Roles/UpdateRoleRequest.php | 2 +- .../UpdateServerBuildConfigurationRequest.php | 2 +- .../Servers/UpdateServerDetailsRequest.php | 2 +- .../Servers/UpdateServerStartupRequest.php | 2 +- .../Application/Users/UpdateUserRequest.php | 2 +- .../Api/Client/Account/UpdateEmailRequest.php | 1 + app/Models/AdminRole.php | 17 +--- app/Models/EggVariable.php | 13 +-- app/Models/Node.php | 84 ++++++++++++------- .../Eggs/Sharing/EggImporterService.php | 2 +- .../Eggs/Variables/VariableUpdateService.php | 3 +- .../Helpers/SoftwareVersionService.php | 2 +- .../Telemetry/TelemetryCollectionService.php | 6 +- .../Application/ServerVariableTransformer.php | 21 ----- ...database_host_id_column_to_nodes_table.php | 4 +- ...658_change_port_columns_on_nodes_table.php | 6 +- phpstan.neon | 3 - 25 files changed, 98 insertions(+), 105 deletions(-) diff --git a/app/Console/Commands/InfoCommand.php b/app/Console/Commands/InfoCommand.php index 25c7744058..6648da2980 100644 --- a/app/Console/Commands/InfoCommand.php +++ b/app/Console/Commands/InfoCommand.php @@ -15,7 +15,7 @@ class InfoCommand extends Command /** * VersionCommand constructor. */ - public function __construct(private ConfigRepository $config, private SoftwareVersionService $versionService) + public function __construct(private ConfigRepository $config, private SoftwareVersionService $softwareVersionService) { parent::__construct(); } @@ -27,9 +27,9 @@ public function handle() { $this->output->title('Version Information'); $this->table([], [ - ['Panel Version', $this->config->get('app.version')], - ['Latest Version', $this->versionService->getPanel()], - ['Up-to-Date', $this->versionService->isLatestPanel() ? 'Yes' : $this->formatText('No', 'bg=red')], + ['Panel Version', $this->softwareVersionService->getCurrentVersion()], + ['Latest Version', $this->softwareVersionService->getLatestPanel()], + ['Up-to-Date', $this->softwareVersionService->isLatestPanel() ? 'Yes' : $this->formatText('No', 'bg=red')], ['Unique Identifier', $this->config->get('pterodactyl.service.author')], ], 'compact'); diff --git a/app/Http/Controllers/Api/Application/Nodes/NodeDeploymentController.php b/app/Http/Controllers/Api/Application/Nodes/NodeDeploymentController.php index fa09e9707b..c3e9303a7f 100644 --- a/app/Http/Controllers/Api/Application/Nodes/NodeDeploymentController.php +++ b/app/Http/Controllers/Api/Application/Nodes/NodeDeploymentController.php @@ -30,7 +30,7 @@ public function __invoke(GetDeployableNodesRequest $request): array $nodes = $this->viableNodesService->setLocations($data['location_ids'] ?? []) ->setMemory($data['memory']) ->setDisk($data['disk']) - ->handle($request->query('per_page'), $request->query('page')); // @phpstan-ignore-line + ->handle($request->query('per_page'), $request->query('page')); return $this->fractal->collection($nodes) ->transformWith(NodeTransformer::class) diff --git a/app/Http/Requests/Api/Application/Databases/UpdateDatabaseRequest.php b/app/Http/Requests/Api/Application/Databases/UpdateDatabaseRequest.php index 4767190057..51b9f1a593 100644 --- a/app/Http/Requests/Api/Application/Databases/UpdateDatabaseRequest.php +++ b/app/Http/Requests/Api/Application/Databases/UpdateDatabaseRequest.php @@ -8,6 +8,6 @@ class UpdateDatabaseRequest extends StoreDatabaseRequest { public function rules(array $rules = null): array { - return $rules ?? DatabaseHost::getRulesForUpdate($this->route()->parameter('databaseHost')->id); + return $rules ?? DatabaseHost::getRulesForUpdate($this->route()->parameter('databaseHost')); } } diff --git a/app/Http/Requests/Api/Application/Locations/UpdateLocationRequest.php b/app/Http/Requests/Api/Application/Locations/UpdateLocationRequest.php index 91ece11fe9..f5d79deb28 100644 --- a/app/Http/Requests/Api/Application/Locations/UpdateLocationRequest.php +++ b/app/Http/Requests/Api/Application/Locations/UpdateLocationRequest.php @@ -8,7 +8,7 @@ class UpdateLocationRequest extends StoreLocationRequest { public function rules(): array { - $locationId = $this->route()->parameter('location')->id; + $locationId = $this->route()->parameter('location'); return collect(Location::getRulesForUpdate($locationId))->only([ 'short', diff --git a/app/Http/Requests/Api/Application/Mounts/UpdateMountRequest.php b/app/Http/Requests/Api/Application/Mounts/UpdateMountRequest.php index b9b51b12c3..c1317f7844 100644 --- a/app/Http/Requests/Api/Application/Mounts/UpdateMountRequest.php +++ b/app/Http/Requests/Api/Application/Mounts/UpdateMountRequest.php @@ -8,6 +8,6 @@ class UpdateMountRequest extends StoreMountRequest { public function rules(array $rules = null): array { - return $rules ?? Mount::getRulesForUpdate($this->route()->parameter('mount')->id); + return $rules ?? Mount::getRulesForUpdate($this->route()->parameter('mount')); } } diff --git a/app/Http/Requests/Api/Application/Nests/UpdateNestRequest.php b/app/Http/Requests/Api/Application/Nests/UpdateNestRequest.php index 1c7aaec89e..30016cd016 100644 --- a/app/Http/Requests/Api/Application/Nests/UpdateNestRequest.php +++ b/app/Http/Requests/Api/Application/Nests/UpdateNestRequest.php @@ -8,6 +8,6 @@ class UpdateNestRequest extends StoreNestRequest { public function rules(array $rules = null): array { - return $rules ?? Nest::getRulesForUpdate($this->route()->parameter('nest')->id); + return $rules ?? Nest::getRulesForUpdate($this->route()->parameter('nest')); } } diff --git a/app/Http/Requests/Api/Application/Nodes/StoreNodeRequest.php b/app/Http/Requests/Api/Application/Nodes/StoreNodeRequest.php index 803f0e6a39..6d76293025 100644 --- a/app/Http/Requests/Api/Application/Nodes/StoreNodeRequest.php +++ b/app/Http/Requests/Api/Application/Nodes/StoreNodeRequest.php @@ -42,10 +42,8 @@ public function rules(array $rules = null): array /** * Fields to rename for clarity in the API response. - * - * @return array */ - public function attributes() + public function attributes(): array { return [ 'daemon_base' => 'Daemon Base Path', @@ -58,13 +56,8 @@ public function attributes() /** * Change the formatting of some data keys in the validated response data * to match what the application expects in the services. - * - * @param string|null $key - * @param string|array|null $default - * - * @return mixed */ - public function validated($key = null, $default = null) + public function validated($key = null, $default = null): array { $response = parent::validated(); $response['daemon_base'] = $response['daemon_base'] ?? Node::DEFAULT_DAEMON_BASE; diff --git a/app/Http/Requests/Api/Application/Nodes/UpdateNodeRequest.php b/app/Http/Requests/Api/Application/Nodes/UpdateNodeRequest.php index 05daebc2eb..ae8aab3574 100644 --- a/app/Http/Requests/Api/Application/Nodes/UpdateNodeRequest.php +++ b/app/Http/Requests/Api/Application/Nodes/UpdateNodeRequest.php @@ -8,6 +8,6 @@ class UpdateNodeRequest extends StoreNodeRequest { public function rules(array $rules = null): array { - return parent::rules($rules ?? Node::getRulesForUpdate($this->route()->parameter('node')->id)); + return parent::rules($rules ?? Node::getRulesForUpdate($this->route()->parameter('node'))); } } diff --git a/app/Http/Requests/Api/Application/Roles/UpdateRoleRequest.php b/app/Http/Requests/Api/Application/Roles/UpdateRoleRequest.php index de3221abd1..de36ff33e3 100644 --- a/app/Http/Requests/Api/Application/Roles/UpdateRoleRequest.php +++ b/app/Http/Requests/Api/Application/Roles/UpdateRoleRequest.php @@ -8,6 +8,6 @@ class UpdateRoleRequest extends StoreRoleRequest { public function rules(array $rules = null): array { - return $rules ?? AdminRole::getRulesForUpdate($this->route()->parameter('role')->id); + return $rules ?? AdminRole::getRulesForUpdate($this->route()->parameter('role')); } } diff --git a/app/Http/Requests/Api/Application/Servers/UpdateServerBuildConfigurationRequest.php b/app/Http/Requests/Api/Application/Servers/UpdateServerBuildConfigurationRequest.php index 1e2f120510..03bbd08ec6 100644 --- a/app/Http/Requests/Api/Application/Servers/UpdateServerBuildConfigurationRequest.php +++ b/app/Http/Requests/Api/Application/Servers/UpdateServerBuildConfigurationRequest.php @@ -13,7 +13,7 @@ class UpdateServerBuildConfigurationRequest extends ServerWriteRequest */ public function rules(): array { - $rules = Server::getRulesForUpdate($this->route()->parameter('server')->id); + $rules = Server::getRulesForUpdate($this->route()->parameter('server')); return [ 'allocation' => $rules['allocation_id'], diff --git a/app/Http/Requests/Api/Application/Servers/UpdateServerDetailsRequest.php b/app/Http/Requests/Api/Application/Servers/UpdateServerDetailsRequest.php index a4551edcd3..ce181bae3e 100644 --- a/app/Http/Requests/Api/Application/Servers/UpdateServerDetailsRequest.php +++ b/app/Http/Requests/Api/Application/Servers/UpdateServerDetailsRequest.php @@ -12,7 +12,7 @@ class UpdateServerDetailsRequest extends ServerWriteRequest */ public function rules(): array { - $rules = Server::getRulesForUpdate($this->route()->parameter('server')->id); + $rules = Server::getRulesForUpdate($this->route()->parameter('server')); return [ 'external_id' => $rules['external_id'], diff --git a/app/Http/Requests/Api/Application/Servers/UpdateServerStartupRequest.php b/app/Http/Requests/Api/Application/Servers/UpdateServerStartupRequest.php index d8f92a1f85..fb16ffdacc 100644 --- a/app/Http/Requests/Api/Application/Servers/UpdateServerStartupRequest.php +++ b/app/Http/Requests/Api/Application/Servers/UpdateServerStartupRequest.php @@ -9,7 +9,7 @@ class UpdateServerStartupRequest extends ApplicationApiRequest { public function rules(): array { - $rules = Server::getRulesForUpdate($this->route()->parameter('server')->id); + $rules = Server::getRulesForUpdate($this->route()->parameter('server')); return [ 'startup' => $rules['startup'], diff --git a/app/Http/Requests/Api/Application/Users/UpdateUserRequest.php b/app/Http/Requests/Api/Application/Users/UpdateUserRequest.php index ba6d9da2c9..3d7d9f75cb 100644 --- a/app/Http/Requests/Api/Application/Users/UpdateUserRequest.php +++ b/app/Http/Requests/Api/Application/Users/UpdateUserRequest.php @@ -8,6 +8,6 @@ class UpdateUserRequest extends StoreUserRequest { public function rules(array $rules = null): array { - return parent::rules($rules ?? User::getRulesForUpdate($this->route()->parameter('user')->id)); + return parent::rules($rules ?? User::getRulesForUpdate($this->route()->parameter('user'))); } } diff --git a/app/Http/Requests/Api/Client/Account/UpdateEmailRequest.php b/app/Http/Requests/Api/Client/Account/UpdateEmailRequest.php index 6287ba5855..083e940580 100644 --- a/app/Http/Requests/Api/Client/Account/UpdateEmailRequest.php +++ b/app/Http/Requests/Api/Client/Account/UpdateEmailRequest.php @@ -11,6 +11,7 @@ class UpdateEmailRequest extends ClientApiRequest { /** + * @throws \Illuminate\Contracts\Container\BindingResolutionException * @throws \Pterodactyl\Exceptions\Http\Base\InvalidPasswordProvidedException */ public function authorize(): bool diff --git a/app/Models/AdminRole.php b/app/Models/AdminRole.php index 51b485495f..f03368392b 100644 --- a/app/Models/AdminRole.php +++ b/app/Models/AdminRole.php @@ -2,6 +2,8 @@ namespace Pterodactyl\Models; +use Illuminate\Database\Eloquent\Relations\HasMany; + /** * @property int $id * @property string $name @@ -19,15 +21,11 @@ class AdminRole extends Model /** * The table associated with the model. - * - * @var string */ protected $table = 'admin_roles'; /** * Fields that are mass assignable. - * - * @var array */ protected $fillable = [ 'name', @@ -37,8 +35,6 @@ class AdminRole extends Model /** * Cast values to correct type. - * - * @var array */ protected $casts = [ 'sort_id' => 'int', @@ -51,17 +47,12 @@ class AdminRole extends Model 'sort_id' => 'sometimes|numeric', ]; - /** - * @var bool - */ public $timestamps = false; /** - * Gets the permissions associated with a admin role. - * - * @return \Illuminate\Database\Eloquent\Relations\HasMany + * Gets the permissions associated with an admin role. */ - public function permissions() + public function permissions(): HasMany { return $this->hasMany(Permission::class); } diff --git a/app/Models/EggVariable.php b/app/Models/EggVariable.php index dbbf8e0bd0..b0e15bc5cc 100644 --- a/app/Models/EggVariable.php +++ b/app/Models/EggVariable.php @@ -2,8 +2,8 @@ namespace Pterodactyl\Models; -use Illuminate\Database\Eloquent\Relations\HasOne; use Illuminate\Database\Eloquent\Relations\HasMany; +use Illuminate\Database\Eloquent\Relations\BelongsTo; /** * @property int $id @@ -19,7 +19,7 @@ * @property \Carbon\CarbonImmutable $updated_at * @property bool $required * @property Egg $egg - * @property ServerVariable $serverVariable + * @property ServerVariable $serverVariables * @property string $field_type * * The "server_value" variable is only present on the object if you've loaded this model @@ -81,15 +81,18 @@ public function getRequiredAttribute(): bool return in_array('required', explode('|', $this->rules)); } - public function egg(): HasOne + /** + * Returns the egg that this variable belongs to. + */ + public function egg(): BelongsTo { - return $this->hasOne(Egg::class); + return $this->belongsTo(Egg::class); } /** * Return server variables associated with this variable. */ - public function serverVariable(): HasMany + public function serverVariables(): HasMany { return $this->hasMany(ServerVariable::class, 'variable_id'); } diff --git a/app/Models/Node.php b/app/Models/Node.php index a1d9688f67..eb7d5cc254 100644 --- a/app/Models/Node.php +++ b/app/Models/Node.php @@ -19,8 +19,13 @@ * @property string $name * @property string|null $description * @property int $location_id - * @property string $fqdn + * @property int|null $database_host_id * @property string $scheme + * @property string $fqdn + * @property int $listen_port_http + * @property int $listen_port_sftp + * @property int $public_port_http + * @property int $public_port_sftp * @property bool $behind_proxy * @property bool $maintenance_mode * @property int $memory @@ -32,17 +37,16 @@ * @property int $upload_size * @property string $daemon_token_id * @property string $daemon_token - * @property int $daemonListen - * @property int $daemonSFTP - * @property string $daemonBase + * @property string $daemon_base * @property int $servers_count * @property \Carbon\Carbon $created_at * @property \Carbon\Carbon $updated_at + * @property Allocation[]|Collection $allocations + * @property \Pterodactyl\Models\DatabaseHost|null $databaseHost * @property Location $location - * @property int[]|\Illuminate\Support\Collection $ports * @property Mount[]|Collection $mounts + * @property int[]|\Illuminate\Support\Collection $ports * @property Server[]|Collection $servers - * @property Allocation[]|Collection $allocations */ class Node extends Model { @@ -54,6 +58,11 @@ class Node extends Model */ public const RESOURCE_NAME = 'node'; + /** + * The default location of server files on the Wings instance. + */ + public const DEFAULT_DAEMON_BASE = '/var/lib/pterodactyl/volumes'; + public const DAEMON_TOKEN_ID_LENGTH = 16; public const DAEMON_TOKEN_LENGTH = 64; @@ -72,10 +81,13 @@ class Node extends Model */ protected $casts = [ 'location_id' => 'integer', + 'database_host_id' => 'integer', + 'listen_port_http' => 'integer', + 'listen_port_sftp' => 'integer', + 'public_port_http' => 'integer', + 'public_port_sftp' => 'integer', 'memory' => 'integer', 'disk' => 'integer', - 'daemonListen' => 'integer', - 'daemonSFTP' => 'integer', 'behind_proxy' => 'boolean', 'public' => 'boolean', 'maintenance_mode' => 'boolean', @@ -85,11 +97,11 @@ class Node extends Model * Fields that are mass assignable. */ protected $fillable = [ - 'public', 'name', 'location_id', + 'public', 'name', 'location_id', 'database_host_id', + 'listen_port_http', 'listen_port_sftp', 'public_port_http', 'public_port_sftp', 'fqdn', 'scheme', 'behind_proxy', 'memory', 'memory_overallocate', 'disk', - 'disk_overallocate', 'upload_size', 'daemonBase', - 'daemonSFTP', 'daemonListen', + 'disk_overallocate', 'upload_size', 'daemon_base', 'description', 'maintenance_mode', ]; @@ -97,17 +109,20 @@ class Node extends Model 'name' => 'required|regex:/^([\w .-]{1,100})$/', 'description' => 'string|nullable', 'location_id' => 'required|exists:locations,id', + 'database_host_id' => 'sometimes|nullable|exists:database_hosts,id', 'public' => 'boolean', 'fqdn' => 'required|string', + 'listen_port_http' => 'required|numeric|between:1,65535', + 'listen_port_sftp' => 'required|numeric|between:1,65535', + 'public_port_http' => 'required|numeric|between:1,65535', + 'public_port_sftp' => 'required|numeric|between:1,65535', 'scheme' => 'required', 'behind_proxy' => 'boolean', 'memory' => 'required|numeric|min:1', 'memory_overallocate' => 'required|numeric|min:-1', 'disk' => 'required|numeric|min:1', 'disk_overallocate' => 'required|numeric|min:-1', - 'daemonBase' => 'sometimes|required|regex:/^([\/][\d\w.\-\/]+)$/', - 'daemonSFTP' => 'required|numeric|between:1,65535', - 'daemonListen' => 'required|numeric|between:1,65535', + 'daemon_base' => 'sometimes|required|regex:/^([\/][\d\w.\-\/]+)$/', 'maintenance_mode' => 'boolean', 'upload_size' => 'int|between:1,1024', ]; @@ -116,13 +131,15 @@ class Node extends Model * Default values for specific columns that are generally not changed on base installs. */ protected $attributes = [ + 'listen_port_http' => 8080, + 'listen_port_sftp' => 2022, + 'public_port_http' => 8080, + 'public_port_sftp' => 2022, 'public' => true, 'behind_proxy' => false, 'memory_overallocate' => 0, 'disk_overallocate' => 0, - 'daemonBase' => '/var/lib/pterodactyl/volumes', - 'daemonSFTP' => 2022, - 'daemonListen' => 8080, + 'daemon_base' => self::DEFAULT_DAEMON_BASE, 'maintenance_mode' => false, ]; @@ -146,7 +163,7 @@ public function getConfiguration(): array 'token' => Container::getInstance()->make(Encrypter::class)->decrypt($this->daemon_token), 'api' => [ 'host' => '0.0.0.0', - 'port' => $this->daemonListen, + 'port' => $this->listen_port_http, 'ssl' => [ 'enabled' => (!$this->behind_proxy && $this->scheme === 'https'), 'cert' => '/etc/letsencrypt/live/' . Str::lower($this->fqdn) . '/fullchain.pem', @@ -155,9 +172,9 @@ public function getConfiguration(): array 'upload_limit' => $this->upload_size, ], 'system' => [ - 'data' => $this->daemonBase, + 'data' => $this->daemon_base, 'sftp' => [ - 'bind_port' => $this->daemonSFTP, + 'bind_port' => $this->listen_port_sftp, ], ], 'allowed_mounts' => $this->mounts->pluck('source')->toArray(), @@ -196,9 +213,20 @@ public function isUnderMaintenance(): bool return $this->maintenance_mode; } - public function mounts(): HasManyThrough + /** + * Gets the allocations associated with a node. + */ + public function allocations(): HasMany { - return $this->hasManyThrough(Mount::class, MountNode::class, 'node_id', 'id', 'id', 'mount_id'); + return $this->hasMany(Allocation::class); + } + + /** + * Returns the database host associated with a node. + */ + public function databaseHost(): BelongsTo + { + return $this->belongsTo(DatabaseHost::class); } /** @@ -210,19 +238,19 @@ public function location(): BelongsTo } /** - * Gets the servers associated with a node. + * Returns a HasManyThrough relationship for all the mounts associated with a node. */ - public function servers(): HasMany + public function mounts(): HasManyThrough { - return $this->hasMany(Server::class); + return $this->hasManyThrough(Mount::class, MountNode::class, 'node_id', 'id', 'id', 'mount_id'); } /** - * Gets the allocations associated with a node. + * Gets the servers associated with a node. */ - public function allocations(): HasMany + public function servers(): HasMany { - return $this->hasMany(Allocation::class); + return $this->hasMany(Server::class); } public function loadServerSums(): self diff --git a/app/Services/Eggs/Sharing/EggImporterService.php b/app/Services/Eggs/Sharing/EggImporterService.php index 8be37f0ab7..57dd5c8c96 100644 --- a/app/Services/Eggs/Sharing/EggImporterService.php +++ b/app/Services/Eggs/Sharing/EggImporterService.php @@ -116,7 +116,7 @@ private function handleArray(int $nestId, array $parsed): Egg 'copy_script_from' => null, ]); - $egg = $this->parser->fillFromParsed($egg, $parsed); + $egg = $this->eggParserService->fillFromParsed($egg, $parsed); $egg->save(); foreach ($parsed['variables'] ?? [] as $variable) { diff --git a/app/Services/Eggs/Variables/VariableUpdateService.php b/app/Services/Eggs/Variables/VariableUpdateService.php index 5d380f4ac1..0bd87b98e4 100644 --- a/app/Services/Eggs/Variables/VariableUpdateService.php +++ b/app/Services/Eggs/Variables/VariableUpdateService.php @@ -8,7 +8,6 @@ use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Traits\Services\ValidatesValidationRules; use Illuminate\Contracts\Validation\Factory as ValidationFactory; -use Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface; use Pterodactyl\Exceptions\Service\Egg\Variable\ReservedVariableNameException; class VariableUpdateService @@ -18,7 +17,7 @@ class VariableUpdateService /** * VariableUpdateService constructor. */ - public function __construct(private EggVariableRepositoryInterface $repository, private ValidationFactory $validator) + public function __construct(private ValidationFactory $validator) { } diff --git a/app/Services/Helpers/SoftwareVersionService.php b/app/Services/Helpers/SoftwareVersionService.php index 5b8a5aa80e..ad5ca37896 100644 --- a/app/Services/Helpers/SoftwareVersionService.php +++ b/app/Services/Helpers/SoftwareVersionService.php @@ -122,7 +122,7 @@ public function getVersionData(): array protected function versionData(): array { return $this->cache->remember(self::GIT_VERSION_CACHE_KEY, CarbonImmutable::now()->addSeconds(15), function () { - $configVersion = config()->get('app.version'); + $configVersion = $this->getCurrentVersion(); if (file_exists(base_path('.git/HEAD'))) { $head = explode(' ', file_get_contents(base_path('.git/HEAD'))); diff --git a/app/Services/Telemetry/TelemetryCollectionService.php b/app/Services/Telemetry/TelemetryCollectionService.php index c2fd128308..016cf0817e 100644 --- a/app/Services/Telemetry/TelemetryCollectionService.php +++ b/app/Services/Telemetry/TelemetryCollectionService.php @@ -16,6 +16,7 @@ use Illuminate\Support\Facades\DB; use Pterodactyl\Models\Allocation; use Illuminate\Support\Facades\Http; +use Pterodactyl\Services\Helpers\SoftwareVersionService; use Pterodactyl\Repositories\Eloquent\SettingsRepository; use Pterodactyl\Repositories\Wings\DaemonConfigurationRepository; @@ -26,7 +27,8 @@ class TelemetryCollectionService */ public function __construct( private DaemonConfigurationRepository $daemonConfigurationRepository, - private SettingsRepository $settingsRepository + private SettingsRepository $settingsRepository, + private SoftwareVersionService $softwareVersionService ) { } @@ -108,7 +110,7 @@ public function collect(): array 'id' => $uuid, 'panel' => [ - 'version' => config('app.version'), + 'version' => $this->softwareVersionService->getCurrentVersion(), 'phpVersion' => phpversion(), 'drivers' => [ diff --git a/app/Transformers/Api/Application/ServerVariableTransformer.php b/app/Transformers/Api/Application/ServerVariableTransformer.php index 7c3d1de75c..eef631a657 100644 --- a/app/Transformers/Api/Application/ServerVariableTransformer.php +++ b/app/Transformers/Api/Application/ServerVariableTransformer.php @@ -2,20 +2,12 @@ namespace Pterodactyl\Transformers\Api\Application; -use League\Fractal\Resource\Item; use Pterodactyl\Models\EggVariable; use Pterodactyl\Models\ServerVariable; -use League\Fractal\Resource\NullResource; -use Pterodactyl\Services\Acl\Api\AdminAcl; use Pterodactyl\Transformers\Api\Transformer; class ServerVariableTransformer extends Transformer { - /** - * List of resources that can be included. - */ - protected array $availableIncludes = ['parent']; - /** * Return the resource name for the JSONAPI output. */ @@ -31,17 +23,4 @@ public function transform(EggVariable $model): array { return $model->toArray(); } - - /** - * Return the parent service variable data. - */ - public function includeParent(EggVariable $variable): Item|NullResource - { - if (!$this->authorize(AdminAcl::RESOURCE_EGGS)) { - return $this->null(); - } - - // TODO: what the fuck? - return $this->item($variable->variable, new EggVariableTransformer()); - } } diff --git a/database/migrations/2021_01_30_193343_add_database_host_id_column_to_nodes_table.php b/database/migrations/2021_01_30_193343_add_database_host_id_column_to_nodes_table.php index e45ad76189..04ce1640e6 100644 --- a/database/migrations/2021_01_30_193343_add_database_host_id_column_to_nodes_table.php +++ b/database/migrations/2021_01_30_193343_add_database_host_id_column_to_nodes_table.php @@ -17,7 +17,7 @@ public function up(): void Schema::table('nodes', function (Blueprint $table) { $table->integer('database_host_id')->nullable()->unsigned()->after('location_id'); - $table->index('database_host_id')->nullable(); + $table->index('database_host_id'); $table->foreign('database_host_id')->references('id')->on('database_hosts')->onDelete('set null'); }); } @@ -34,7 +34,7 @@ public function down(): void Schema::table('database_hosts', function (Blueprint $table) { $table->integer('node_id')->nullable()->unsigned()->after('max_databases'); - $table->index('node_id')->nullable(); + $table->index('node_id'); $table->foreign('node_id')->references('id')->on('nodes'); }); } diff --git a/database/migrations/2021_02_23_212658_change_port_columns_on_nodes_table.php b/database/migrations/2021_02_23_212658_change_port_columns_on_nodes_table.php index 2e8010c9b6..1a61354b7c 100644 --- a/database/migrations/2021_02_23_212658_change_port_columns_on_nodes_table.php +++ b/database/migrations/2021_02_23_212658_change_port_columns_on_nodes_table.php @@ -19,10 +19,10 @@ public function up(): void Schema::table('nodes', function (Blueprint $table) { $table->integer('listen_port_http')->unsigned()->default(8080)->after('fqdn')->change(); - $table->integer('listen_port_sftp')->unsigned()->default(2022)->after('listen_port_sftp')->change(); + $table->integer('listen_port_sftp')->unsigned()->default(2022)->after('listen_port_http')->change(); - $table->integer('public_port_http')->unsigned()->default(8080)->after('listen_port_http'); - $table->integer('public_port_sftp')->unsigned()->default(2022)->after('listen_port_sftp'); + $table->integer('public_port_http')->unsigned()->default(8080)->after('listen_port_sftp'); + $table->integer('public_port_sftp')->unsigned()->default(2022)->after('public_port_http'); }); DB::transaction(function () { diff --git a/phpstan.neon b/phpstan.neon index e12e72eb50..1a394a28fe 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -19,9 +19,6 @@ parameters: # Ignore magic spatie calls - '#Call to an undefined method Illuminate\\Database\\Eloquent\\Builder::allowed(\w+)\(\)#' - # This should be replaced with resources instead of a magic transformer factory, robots in disguise - - '#Method Pterodactyl\\Http\\Controllers\\Api\\Client\\ClientApiController::getTransformer\(\) should return T#' - excludePaths: - app/Repositories From 3670dc6d4bc82a75446bde94684510e229a95d92 Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Wed, 14 Dec 2022 18:08:50 -0700 Subject: [PATCH 336/458] phpstan --- .../Servers/Databases/StoreServerDatabaseRequest.php | 1 + .../Api/Client/Servers/Files/DownloadFileRequest.php | 2 +- app/Models/Node.php | 2 +- app/Transformers/Api/Application/NodeTransformer.php | 8 +------- app/Transformers/Api/Client/ServerTransformer.php | 2 +- 5 files changed, 5 insertions(+), 10 deletions(-) diff --git a/app/Http/Requests/Api/Application/Servers/Databases/StoreServerDatabaseRequest.php b/app/Http/Requests/Api/Application/Servers/Databases/StoreServerDatabaseRequest.php index 17368cd083..706af5de65 100644 --- a/app/Http/Requests/Api/Application/Servers/Databases/StoreServerDatabaseRequest.php +++ b/app/Http/Requests/Api/Application/Servers/Databases/StoreServerDatabaseRequest.php @@ -14,6 +14,7 @@ class StoreServerDatabaseRequest extends ApplicationApiRequest { public function rules(): array { + /** @var \Pterodactyl\Models\Server $server */ $server = $this->route()->parameter('server'); return [ diff --git a/app/Http/Requests/Api/Client/Servers/Files/DownloadFileRequest.php b/app/Http/Requests/Api/Client/Servers/Files/DownloadFileRequest.php index c588c9b23f..af1022bdc8 100644 --- a/app/Http/Requests/Api/Client/Servers/Files/DownloadFileRequest.php +++ b/app/Http/Requests/Api/Client/Servers/Files/DownloadFileRequest.php @@ -13,6 +13,6 @@ class DownloadFileRequest extends ClientApiRequest */ public function authorize(): bool { - return $this->user()->can('file.read', $this->parameter('server', Server::class)); + return $this->user()->can('file.read', $this->route()->parameter('server')); } } diff --git a/app/Models/Node.php b/app/Models/Node.php index eb7d5cc254..1ab72a5bdb 100644 --- a/app/Models/Node.php +++ b/app/Models/Node.php @@ -148,7 +148,7 @@ class Node extends Model */ public function getConnectionAddress(): string { - return sprintf('%s://%s:%s', $this->scheme, $this->fqdn, $this->daemonListen); + return sprintf('%s://%s:%s', $this->scheme, $this->fqdn, $this->public_port_http); } /** diff --git a/app/Transformers/Api/Application/NodeTransformer.php b/app/Transformers/Api/Application/NodeTransformer.php index de3fd63da4..093fcc9621 100644 --- a/app/Transformers/Api/Application/NodeTransformer.php +++ b/app/Transformers/Api/Application/NodeTransformer.php @@ -30,13 +30,7 @@ public function getResourceName(): string */ public function transform(Node $model): array { - $response = collect($model->toArray())->mapWithKeys(function ($value, $key) { - // I messed up early in 2016 when I named this column as poorly - // as I did. This is the tragic result of my mistakes. - $key = ($key === 'daemonSFTP') ? 'daemonSftp' : $key; - - return [snake_case($key) => $value]; - })->toArray(); + $response = $model->toArray(); $response['created_at'] = self::formatTimestamp($model->created_at); $response['updated_at'] = self::formatTimestamp($model->updated_at); diff --git a/app/Transformers/Api/Client/ServerTransformer.php b/app/Transformers/Api/Client/ServerTransformer.php index 401c349052..01a2d135b4 100644 --- a/app/Transformers/Api/Client/ServerTransformer.php +++ b/app/Transformers/Api/Client/ServerTransformer.php @@ -45,7 +45,7 @@ public function transform(Server $server): array 'is_node_under_maintenance' => $server->node->isUnderMaintenance(), 'sftp_details' => [ 'ip' => $server->node->fqdn, - 'port' => $server->node->daemonSFTP, + 'port' => $server->node->public_port_sftp, ], 'description' => $server->description, 'limits' => [ From f68c46b0a033c07dc71e7be9ffa1db61d9fd5df0 Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Wed, 14 Dec 2022 18:10:12 -0700 Subject: [PATCH 337/458] eggs: remove `config_logs` field --- app/Models/Egg.php | 17 ----------------- app/Services/Eggs/EggParserService.php | 1 - .../Eggs/Sharing/EggExporterService.php | 1 - .../Api/Application/EggTransformer.php | 1 - 4 files changed, 20 deletions(-) diff --git a/app/Models/Egg.php b/app/Models/Egg.php index c33e5b194c..9ddc2adab2 100644 --- a/app/Models/Egg.php +++ b/app/Models/Egg.php @@ -20,7 +20,6 @@ * @property array|null $file_denylist * @property string|null $config_files * @property string|null $config_startup - * @property string|null $config_logs * @property string|null $config_stop * @property int|null $config_from * @property string|null $startup @@ -36,7 +35,6 @@ * @property string $copy_script_container * @property string|null $inherit_config_files * @property string|null $inherit_config_startup - * @property string|null $inherit_config_logs * @property string|null $inherit_config_stop * @property string $inherit_file_denylist * @property array|null $inherit_features @@ -88,7 +86,6 @@ class Egg extends Model 'file_denylist', 'config_files', 'config_startup', - 'config_logs', 'config_stop', 'config_from', 'startup', @@ -128,7 +125,6 @@ class Egg extends Model 'config_from' => 'sometimes|bail|nullable|numeric|exists:eggs,id', 'config_stop' => 'required_without:config_from|nullable|string|max:191', 'config_startup' => 'required_without:config_from|nullable|json', - 'config_logs' => 'required_without:config_from|nullable|json', 'config_files' => 'required_without:config_from|nullable|json', 'update_url' => 'sometimes|nullable|string', 'force_outgoing_ip' => 'sometimes|boolean', @@ -139,7 +135,6 @@ class Egg extends Model 'file_denylist' => null, 'config_stop' => null, 'config_startup' => null, - 'config_logs' => null, 'config_files' => null, 'update_url' => null, ]; @@ -207,18 +202,6 @@ public function getInheritConfigStartupAttribute(): ?string return $this->configFrom->config_startup; } - /** - * Return the log reading configuration for an egg. - */ - public function getInheritConfigLogsAttribute(): ?string - { - if (!is_null($this->config_logs) || is_null($this->config_from)) { - return $this->config_logs; - } - - return $this->configFrom->config_logs; - } - /** * Return the stop command configuration for an egg. */ diff --git a/app/Services/Eggs/EggParserService.php b/app/Services/Eggs/EggParserService.php index c442b8e09e..0890c39638 100644 --- a/app/Services/Eggs/EggParserService.php +++ b/app/Services/Eggs/EggParserService.php @@ -38,7 +38,6 @@ public function fillFromParsed(Egg $model, array $parsed): Egg 'update_url' => Arr::get($parsed, 'meta.update_url'), 'config_files' => Arr::get($parsed, 'config.files'), 'config_startup' => Arr::get($parsed, 'config.startup'), - 'config_logs' => Arr::get($parsed, 'config.logs'), 'config_stop' => Arr::get($parsed, 'config.stop'), 'startup' => Arr::get($parsed, 'startup'), 'script_install' => Arr::get($parsed, 'scripts.installation.script'), diff --git a/app/Services/Eggs/Sharing/EggExporterService.php b/app/Services/Eggs/Sharing/EggExporterService.php index 706297b3de..d52fe73713 100644 --- a/app/Services/Eggs/Sharing/EggExporterService.php +++ b/app/Services/Eggs/Sharing/EggExporterService.php @@ -45,7 +45,6 @@ public function handle(int $egg): string 'config' => [ 'files' => $egg->inherit_config_files, 'startup' => $egg->inherit_config_startup, - 'logs' => $egg->inherit_config_logs, 'stop' => $egg->inherit_config_stop, ], 'scripts' => [ diff --git a/app/Transformers/Api/Application/EggTransformer.php b/app/Transformers/Api/Application/EggTransformer.php index c98a5d16ac..ab7328f944 100644 --- a/app/Transformers/Api/Application/EggTransformer.php +++ b/app/Transformers/Api/Application/EggTransformer.php @@ -86,7 +86,6 @@ public function includeConfig(Egg $model): Item|NullResource 'files' => json_decode($model->inherit_config_files), 'startup' => json_decode($model->inherit_config_startup), 'stop' => $model->inherit_config_stop, - 'logs' => json_decode($model->inherit_config_logs), ]; }); } From 2f15d949578efc1138be343093289e85bf92831a Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Wed, 14 Dec 2022 18:17:16 -0700 Subject: [PATCH 338/458] database: fix migrations with postgres --- ..._23_212658_change_port_columns_on_nodes_table.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/database/migrations/2021_02_23_212658_change_port_columns_on_nodes_table.php b/database/migrations/2021_02_23_212658_change_port_columns_on_nodes_table.php index 1a61354b7c..8eeef09270 100644 --- a/database/migrations/2021_02_23_212658_change_port_columns_on_nodes_table.php +++ b/database/migrations/2021_02_23_212658_change_port_columns_on_nodes_table.php @@ -12,9 +12,9 @@ public function up(): void { Schema::table('nodes', function (Blueprint $table) { - $table->renameColumn('daemonListen', 'listen_port_http'); - $table->renameColumn('daemonSFTP', 'listen_port_sftp'); - $table->renameColumn('daemonBase', 'daemon_base'); + $table->renameColumn('`daemonListen`', 'listen_port_http'); + $table->renameColumn('`daemonSFTP`', 'listen_port_sftp'); + $table->renameColumn('`daemonBase`', 'daemon_base'); }); Schema::table('nodes', function (Blueprint $table) { @@ -42,9 +42,9 @@ public function up(): void public function down(): void { Schema::table('nodes', function (Blueprint $table) { - $table->renameColumn('listen_port_http', 'daemonListen'); - $table->renameColumn('listen_port_sftp', 'daemonSFTP'); - $table->renameColumn('daemon_base', 'daemonBase'); + $table->renameColumn('listen_port_http', '`daemonListen`'); + $table->renameColumn('listen_port_sftp', '`daemonSFTP`'); + $table->renameColumn('daemon_base', '`daemonBase`'); $table->dropColumn('public_port_http'); $table->dropColumn('public_port_sftp'); From 6b11836a410735ecf29b96203a093e0aa80501b5 Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Wed, 14 Dec 2022 18:17:27 -0700 Subject: [PATCH 339/458] user: remove `name_first` and `name_last` --- app/Console/Commands/User/DeleteUserCommand.php | 4 ++-- app/Console/Commands/User/MakeUserCommand.php | 5 +---- app/Models/User.php | 9 --------- app/Notifications/AccountCreated.php | 2 +- app/Notifications/MailTested.php | 2 +- app/Observers/SubuserObserver.php | 4 ++-- app/Services/Subusers/SubuserCreationService.php | 2 -- app/Transformers/Api/Client/AccountTransformer.php | 2 -- 8 files changed, 7 insertions(+), 23 deletions(-) diff --git a/app/Console/Commands/User/DeleteUserCommand.php b/app/Console/Commands/User/DeleteUserCommand.php index 2b13b48661..bac97ddaf4 100644 --- a/app/Console/Commands/User/DeleteUserCommand.php +++ b/app/Console/Commands/User/DeleteUserCommand.php @@ -44,10 +44,10 @@ public function handle(): int if ($this->input->isInteractive()) { $tableValues = []; foreach ($results as $user) { - $tableValues[] = [$user->id, $user->email, $user->name]; + $tableValues[] = [$user->id, $user->email, $user->username]; } - $this->table(['User ID', 'Email', 'Name'], $tableValues); + $this->table(['User ID', 'Email', 'Username'], $tableValues); if (!$deleteUser = $this->ask(trans('command/messages.user.select_search_user'))) { return $this->handle(); } diff --git a/app/Console/Commands/User/MakeUserCommand.php b/app/Console/Commands/User/MakeUserCommand.php index 635a956467..f37de218b8 100644 --- a/app/Console/Commands/User/MakeUserCommand.php +++ b/app/Console/Commands/User/MakeUserCommand.php @@ -30,8 +30,6 @@ public function handle() $root_admin = $this->option('admin') ?? $this->confirm(trans('command/messages.user.ask_admin')); $email = $this->option('email') ?? $this->ask(trans('command/messages.user.ask_email')); $username = $this->option('username') ?? $this->ask(trans('command/messages.user.ask_username')); - $name_first = $this->option('name-first') ?? $this->ask(trans('command/messages.user.ask_name_first')); - $name_last = $this->option('name-last') ?? $this->ask(trans('command/messages.user.ask_name_last')); if (is_null($password = $this->option('password')) && !$this->option('no-password')) { $this->warn(trans('command/messages.user.ask_password_help')); @@ -39,12 +37,11 @@ public function handle() $password = $this->secret(trans('command/messages.user.ask_password')); } - $user = $this->creationService->handle(compact('email', 'username', 'name_first', 'name_last', 'password', 'root_admin')); + $user = $this->creationService->handle(compact('email', 'username', 'password', 'root_admin')); $this->table(['Field', 'Value'], [ ['UUID', $user->uuid], ['Email', $user->email], ['Username', $user->username], - ['Name', $user->name], ['Admin', $user->root_admin ? 'Yes' : 'No'], ]); } diff --git a/app/Models/User.php b/app/Models/User.php index 50361af8bf..fa6ebffa16 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -43,7 +43,6 @@ * @property \Illuminate\Support\Carbon|null $updated_at * @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\ApiKey[] $apiKeys * @property int|null $api_keys_count - * @property string $name * @property \Illuminate\Notifications\DatabaseNotificationCollection|\Illuminate\Notifications\DatabaseNotification[] $notifications * @property int|null $notifications_count * @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\RecoveryToken[] $recoveryTokens @@ -223,14 +222,6 @@ public function setUsernameAttribute(string $value) $this->attributes['username'] = mb_strtolower($value); } - /** - * Return a concatenated result for the accounts full name. - */ - public function getNameAttribute(): string - { - return trim($this->name_first . ' ' . $this->name_last); - } - public function avatarURL(): string { return 'https://www.gravatar.com/avatar/' . md5($this->email) . '.jpg'; diff --git a/app/Notifications/AccountCreated.php b/app/Notifications/AccountCreated.php index fb13380121..50d7f1f428 100644 --- a/app/Notifications/AccountCreated.php +++ b/app/Notifications/AccountCreated.php @@ -33,7 +33,7 @@ public function via(): array public function toMail(): MailMessage { $message = (new MailMessage()) - ->greeting('Hello ' . $this->user->name . '!') + ->greeting('Hello!') ->line('You are receiving this email because an account has been created for you on ' . config('app.name') . '.') ->line('Username: ' . $this->user->username) ->line('Email: ' . $this->user->email); diff --git a/app/Notifications/MailTested.php b/app/Notifications/MailTested.php index 892ff7b0a2..3ef25a57bd 100644 --- a/app/Notifications/MailTested.php +++ b/app/Notifications/MailTested.php @@ -21,7 +21,7 @@ public function toMail(): MailMessage { return (new MailMessage()) ->subject('Pterodactyl Test Message') - ->greeting('Hello ' . $this->user->name . '!') + ->greeting('Hello ' . $this->user->username . '!') ->line('This is a test of the Pterodactyl mail system. You\'re good to go!'); } } diff --git a/app/Observers/SubuserObserver.php b/app/Observers/SubuserObserver.php index f1e028b96f..efb24d9475 100644 --- a/app/Observers/SubuserObserver.php +++ b/app/Observers/SubuserObserver.php @@ -25,7 +25,7 @@ public function created(Subuser $subuser): void event(new Events\Subuser\Created($subuser)); $subuser->user->notify(new AddedToServer([ - 'user' => $subuser->user->name_first, + 'user' => $subuser->user->username, 'name' => $subuser->server->name, 'uuidShort' => $subuser->server->uuidShort, ])); @@ -47,7 +47,7 @@ public function deleted(Subuser $subuser): void event(new Events\Subuser\Deleted($subuser)); $subuser->user->notify(new RemovedFromServer([ - 'user' => $subuser->user->name_first, + 'user' => $subuser->user->username, 'name' => $subuser->server->name, ])); } diff --git a/app/Services/Subusers/SubuserCreationService.php b/app/Services/Subusers/SubuserCreationService.php index c207b0d071..e45f2813aa 100644 --- a/app/Services/Subusers/SubuserCreationService.php +++ b/app/Services/Subusers/SubuserCreationService.php @@ -58,8 +58,6 @@ public function handle(Server $server, string $email, array $permissions): Subus $user = $this->userCreationService->handle([ 'email' => $email, 'username' => $username, - 'name_first' => 'Server', - 'name_last' => 'Subuser', 'root_admin' => false, ]); } diff --git a/app/Transformers/Api/Client/AccountTransformer.php b/app/Transformers/Api/Client/AccountTransformer.php index 68651d8f33..3a78792d60 100644 --- a/app/Transformers/Api/Client/AccountTransformer.php +++ b/app/Transformers/Api/Client/AccountTransformer.php @@ -25,8 +25,6 @@ public function transform(User $model): array 'admin' => $model->root_admin, 'username' => $model->username, 'email' => $model->email, - 'first_name' => $model->name_first, - 'last_name' => $model->name_last, 'language' => $model->language, ]; } From a24c594cbdeb6b54edffc2d40482f5ae30c48327 Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Wed, 14 Dec 2022 18:23:46 -0700 Subject: [PATCH 340/458] user: remove `name_first` and `name_last` from tests --- database/Factories/NodeFactory.php | 8 +++--- database/Factories/UserFactory.php | 2 -- storage/app/packs/.githold | 0 .../Users/ExternalUserControllerTest.php | 4 +-- .../Application/Users/UserControllerTest.php | 26 ++++++------------- .../Api/Client/AccountControllerTest.php | 2 -- 6 files changed, 14 insertions(+), 28 deletions(-) delete mode 100755 storage/app/packs/.githold diff --git a/database/Factories/NodeFactory.php b/database/Factories/NodeFactory.php index 1dbd5d9f5e..65786f702f 100644 --- a/database/Factories/NodeFactory.php +++ b/database/Factories/NodeFactory.php @@ -27,6 +27,10 @@ public function definition(): array 'public' => true, 'name' => 'FactoryNode_' . Str::random(10), 'fqdn' => $this->faker->unique()->ipv4, + 'listen_port_http' => 8080, + 'listen_port_sftp' => 2022, + 'public_port_http' => 8080, + 'public_port_sftp' => 2022, 'scheme' => 'http', 'behind_proxy' => false, 'memory' => 1024, @@ -36,9 +40,7 @@ public function definition(): array 'upload_size' => 100, 'daemon_token_id' => Str::random(Node::DAEMON_TOKEN_ID_LENGTH), 'daemon_token' => Crypt::encrypt(Str::random(Node::DAEMON_TOKEN_LENGTH)), - 'daemonListen' => 8080, - 'daemonSFTP' => 2022, - 'daemonBase' => '/var/lib/pterodactyl/volumes', + 'daemon_base' => Node::DEFAULT_DAEMON_BASE, ]; } } diff --git a/database/Factories/UserFactory.php b/database/Factories/UserFactory.php index 1e38eced6c..7cb3f0eda3 100644 --- a/database/Factories/UserFactory.php +++ b/database/Factories/UserFactory.php @@ -29,8 +29,6 @@ public function definition(): array 'uuid' => Uuid::uuid4()->toString(), 'username' => $this->faker->userName . '_' . Str::random(10), 'email' => Str::random(32) . '@example.com', - 'name_first' => $this->faker->firstName, - 'name_last' => $this->faker->lastName, 'password' => $password ?: $password = bcrypt('password'), 'language' => 'en', 'root_admin' => false, diff --git a/storage/app/packs/.githold b/storage/app/packs/.githold deleted file mode 100755 index e69de29bb2..0000000000 diff --git a/tests/Integration/Api/Application/Users/ExternalUserControllerTest.php b/tests/Integration/Api/Application/Users/ExternalUserControllerTest.php index 1c02df7fca..053ba36eea 100644 --- a/tests/Integration/Api/Application/Users/ExternalUserControllerTest.php +++ b/tests/Integration/Api/Application/Users/ExternalUserControllerTest.php @@ -22,7 +22,7 @@ public function testGetRemoteUser() $response->assertJsonStructure([ 'object', 'attributes' => [ - 'id', 'external_id', 'uuid', 'username', 'email', 'first_name', 'last_name', + 'id', 'external_id', 'uuid', 'username', 'email', 'language', 'root_admin', '2fa', 'created_at', 'updated_at', ], ]); @@ -35,8 +35,6 @@ public function testGetRemoteUser() 'uuid' => $user->uuid, 'username' => $user->username, 'email' => $user->email, - 'first_name' => $user->name_first, - 'last_name' => $user->name_last, 'language' => $user->language, 'root_admin' => (bool) $user->root_admin, '2fa' => (bool) $user->totp_enabled, diff --git a/tests/Integration/Api/Application/Users/UserControllerTest.php b/tests/Integration/Api/Application/Users/UserControllerTest.php index 0aebe3d9b5..60f104201b 100644 --- a/tests/Integration/Api/Application/Users/UserControllerTest.php +++ b/tests/Integration/Api/Application/Users/UserControllerTest.php @@ -24,8 +24,8 @@ public function testGetUsers() $response->assertJsonStructure([ 'object', 'data' => [ - ['object', 'attributes' => ['id', 'external_id', 'uuid', 'username', 'email', 'first_name', 'last_name', 'language', 'root_admin', '2fa', 'created_at', 'updated_at']], - ['object', 'attributes' => ['id', 'external_id', 'uuid', 'username', 'email', 'first_name', 'last_name', 'language', 'root_admin', '2fa', 'created_at', 'updated_at']], + ['object', 'attributes' => ['id', 'external_id', 'uuid', 'username', 'email', 'language', 'root_admin', '2fa', 'created_at', 'updated_at']], + ['object', 'attributes' => ['id', 'external_id', 'uuid', 'username', 'email', 'language', 'root_admin', '2fa', 'created_at', 'updated_at']], ], 'meta' => ['pagination' => ['total', 'count', 'per_page', 'current_page', 'total_pages']], ]); @@ -52,11 +52,9 @@ public function testGetUsers() 'uuid' => $this->getApiUser()->uuid, 'username' => $this->getApiUser()->username, 'email' => $this->getApiUser()->email, - 'first_name' => $this->getApiUser()->name_first, - 'last_name' => $this->getApiUser()->name_last, 'language' => $this->getApiUser()->language, 'root_admin' => $this->getApiUser()->root_admin, - '2fa' => (bool) $this->getApiUser()->totp_enabled, + '2fa' => $this->getApiUser()->use_totp, 'created_at' => $this->formatTimestamp($this->getApiUser()->created_at), 'updated_at' => $this->formatTimestamp($this->getApiUser()->updated_at), ], @@ -69,11 +67,9 @@ public function testGetUsers() 'uuid' => $user->uuid, 'username' => $user->username, 'email' => $user->email, - 'first_name' => $user->name_first, - 'last_name' => $user->name_last, 'language' => $user->language, 'root_admin' => (bool) $user->root_admin, - '2fa' => (bool) $user->totp_enabled, + '2fa' => (bool) $user->use_totp, 'created_at' => $this->formatTimestamp($user->created_at), 'updated_at' => $this->formatTimestamp($user->updated_at), ], @@ -92,7 +88,7 @@ public function testGetSingleUser() $response->assertJsonCount(2); $response->assertJsonStructure([ 'object', - 'attributes' => ['id', 'external_id', 'uuid', 'username', 'email', 'first_name', 'last_name', 'language', 'root_admin', '2fa', 'created_at', 'updated_at'], + 'attributes' => ['id', 'external_id', 'uuid', 'username', 'email', 'language', 'root_admin', '2fa', 'created_at', 'updated_at'], ]); $response->assertJson([ @@ -103,8 +99,6 @@ public function testGetSingleUser() 'uuid' => $user->uuid, 'username' => $user->username, 'email' => $user->email, - 'first_name' => $user->name_first, - 'last_name' => $user->name_last, 'language' => $user->language, 'root_admin' => (bool) $user->root_admin, '2fa' => (bool) $user->totp_enabled, @@ -128,7 +122,7 @@ public function testRelationshipsCanBeLoaded() $response->assertJsonStructure([ 'object', 'attributes' => [ - 'id', 'external_id', 'uuid', 'username', 'email', 'first_name', 'last_name', 'language', 'root_admin', '2fa', 'created_at', 'updated_at', + 'id', 'external_id', 'uuid', 'username', 'email', 'language', 'root_admin', '2fa', 'created_at', 'updated_at', 'relationships' => ['servers' => ['object', 'data' => [['object', 'attributes' => []]]]], ], ]); @@ -209,15 +203,13 @@ public function testCreateUser() $response = $this->postJson('/api/application/users', [ 'username' => 'testuser', 'email' => 'test@example.com', - 'first_name' => 'Test', - 'last_name' => 'User', ]); $response->assertStatus(Response::HTTP_CREATED); $response->assertJsonCount(3); $response->assertJsonStructure([ 'object', - 'attributes' => ['id', 'external_id', 'uuid', 'username', 'email', 'first_name', 'last_name', 'language', 'root_admin', '2fa', 'created_at', 'updated_at'], + 'attributes' => ['id', 'external_id', 'uuid', 'username', 'email', 'language', 'root_admin', '2fa', 'created_at', 'updated_at'], 'meta' => ['resource'], ]); @@ -243,14 +235,12 @@ public function testUpdateUser() $response = $this->patchJson('/api/application/users/' . $user->id, [ 'username' => 'new.test.name', 'email' => 'new@emailtest.com', - 'first_name' => $user->name_first, - 'last_name' => $user->name_last, ]); $response->assertStatus(Response::HTTP_OK); $response->assertJsonCount(2); $response->assertJsonStructure([ 'object', - 'attributes' => ['id', 'external_id', 'uuid', 'username', 'email', 'first_name', 'last_name', 'language', 'root_admin', '2fa', 'created_at', 'updated_at'], + 'attributes' => ['id', 'external_id', 'uuid', 'username', 'email', 'language', 'root_admin', '2fa', 'created_at', 'updated_at'], ]); $this->assertDatabaseHas('users', ['username' => 'new.test.name', 'email' => 'new@emailtest.com']); diff --git a/tests/Integration/Api/Client/AccountControllerTest.php b/tests/Integration/Api/Client/AccountControllerTest.php index 0fe1813a3e..6c297648d6 100644 --- a/tests/Integration/Api/Client/AccountControllerTest.php +++ b/tests/Integration/Api/Client/AccountControllerTest.php @@ -26,8 +26,6 @@ public function testAccountDetailsAreReturned() 'admin' => false, 'username' => $user->username, 'email' => $user->email, - 'first_name' => $user->name_first, - 'last_name' => $user->name_last, 'language' => $user->language, ], ]); From 160c3ddefffa6547ec74ec6ab01644fad54c4c4d Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Wed, 14 Dec 2022 18:41:39 -0700 Subject: [PATCH 341/458] composer: update dependencies --- app/Exceptions/DisplayException.php | 3 - .../RequireTwoFactorAuthentication.php | 10 - composer.json | 44 +-- composer.lock | 374 ++++++++---------- config/app.php | 6 - config/prologue/alerts.php | 37 -- phpstan.neon | 6 - 7 files changed, 176 insertions(+), 304 deletions(-) delete mode 100644 config/prologue/alerts.php diff --git a/app/Exceptions/DisplayException.php b/app/Exceptions/DisplayException.php index c1440dbe08..5ea2140d15 100644 --- a/app/Exceptions/DisplayException.php +++ b/app/Exceptions/DisplayException.php @@ -9,7 +9,6 @@ use Illuminate\Http\JsonResponse; use Illuminate\Container\Container; use Illuminate\Http\RedirectResponse; -use Prologue\Alerts\AlertsMessageBag; use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; class DisplayException extends PterodactylException implements HttpExceptionInterface @@ -53,8 +52,6 @@ public function render(Request $request): JsonResponse|RedirectResponse return response()->json(Handler::toArray($this), $this->getStatusCode(), $this->getHeaders()); } - app(AlertsMessageBag::class)->danger($this->getMessage())->flash(); - return redirect()->back()->withInput(); } diff --git a/app/Http/Middleware/RequireTwoFactorAuthentication.php b/app/Http/Middleware/RequireTwoFactorAuthentication.php index 1e68562735..a6110edb16 100644 --- a/app/Http/Middleware/RequireTwoFactorAuthentication.php +++ b/app/Http/Middleware/RequireTwoFactorAuthentication.php @@ -5,7 +5,6 @@ use Illuminate\Support\Str; use Illuminate\Http\Request; use Pterodactyl\Models\User; -use Prologue\Alerts\AlertsMessageBag; use Pterodactyl\Exceptions\Http\TwoFactorAuthRequiredException; class RequireTwoFactorAuthentication @@ -19,13 +18,6 @@ class RequireTwoFactorAuthentication */ protected string $redirectRoute = '/account'; - /** - * RequireTwoFactorAuthentication constructor. - */ - public function __construct(private AlertsMessageBag $alert) - { - } - /** * Check the user state on the incoming request to determine if they should be allowed to * proceed or not. This checks if the Panel is configured to require 2FA on an account in @@ -66,8 +58,6 @@ public function handle(Request $request, \Closure $next): mixed throw new TwoFactorAuthRequiredException(); } - $this->alert->danger(trans('auth.2fa_must_be_enabled'))->flash(); - return redirect()->to($this->redirectRoute); } } diff --git a/composer.json b/composer.json index ee181af3f8..cf82119e75 100644 --- a/composer.json +++ b/composer.json @@ -24,33 +24,32 @@ "ext-pdo_mysql": "*", "ext-posix": "*", "ext-zip": "*", - "aws/aws-sdk-php": "~3.238.2", - "doctrine/dbal": "~3.4.5", - "guzzlehttp/guzzle": "~7.5.0", - "hashids/hashids": "~4.1.0", - "laracasts/utilities": "~3.2.1", - "laravel/framework": "^9.34.0", - "laravel/helpers": "~1.5.0", - "laravel/sanctum": "~2.15.1", - "laravel/tinker": "~2.7.2", - "laravel/ui": "~3.4.6", - "lcobucci/jwt": "~4.2.1", - "league/flysystem-aws-s3-v3": "~3.5.0", - "league/flysystem-memory": "~3.3.0", + "aws/aws-sdk-php": "~3.253", + "doctrine/dbal": "~3.5", + "guzzlehttp/guzzle": "~7.5", + "hashids/hashids": "~4.1", + "laracasts/utilities": "~3.2", + "laravel/framework": "~9.43", + "laravel/helpers": "~1.5", + "laravel/sanctum": "~3.0", + "laravel/tinker": "~2.7", + "laravel/ui": "~4.1", + "lcobucci/jwt": "~4.2", + "league/flysystem-aws-s3-v3": "~3.10", + "league/flysystem-memory": "~3.10", "matriphe/iso-639": "~1.2", "phpseclib/phpseclib": "~3.0", - "pragmarx/google2fa": "~8.0.0", - "predis/predis": "~2.0.2", - "prologue/alerts": "~1.0.0", - "psr/cache": "~3.0.0", - "s1lentium/iptools": "~1.1.1", - "spatie/laravel-fractal": "~6.0.2", - "spatie/laravel-query-builder": "~5.0.3", - "staudenmeir/belongs-to-through": "~2.12.1", + "pragmarx/google2fa": "~8.0", + "predis/predis": "~2.0", + "psr/cache": "~3.0", + "s1lentium/iptools": "~1.1", + "spatie/laravel-fractal": "~6.0", + "spatie/laravel-query-builder": "~5.1", + "staudenmeir/belongs-to-through": "~2.12", "symfony/http-client": "~6.0", "symfony/mailgun-mailer": "~6.0", "symfony/postmark-mailer": "~6.0", - "symfony/yaml": "~5.4", + "symfony/yaml": "~6.0", "webmozart/assert": "~1.11" }, "require-dev": { @@ -62,6 +61,7 @@ "mockery/mockery": "~1.5", "nunomaduro/collision": "~6.3", "nunomaduro/larastan": "^2.0", + "phpstan/phpstan": "~1.9", "php-mock/php-mock-phpunit": "~2.6", "phpunit/phpunit": "~9.5", "spatie/laravel-ignition": "~1.5" diff --git a/composer.lock b/composer.lock index c2a32a9e64..4ec57b684f 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "135afa6c295702de5778650eb2324492", + "content-hash": "a6235c87dc288858ed2c6e8413ab868a", "packages": [ { "name": "aws/aws-crt-php", @@ -58,16 +58,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.238.6", + "version": "3.253.2", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "79a76b438bd20ae687394561b4f28cb6c10db08e" + "reference": "0f0e24bfae22edcdd62bcaedaff9610f8a328952" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/79a76b438bd20ae687394561b4f28cb6c10db08e", - "reference": "79a76b438bd20ae687394561b4f28cb6c10db08e", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/0f0e24bfae22edcdd62bcaedaff9610f8a328952", + "reference": "0f0e24bfae22edcdd62bcaedaff9610f8a328952", "shasum": "" }, "require": { @@ -146,9 +146,9 @@ "support": { "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.238.6" + "source": "https://github.com/aws/aws-sdk-php/tree/3.253.2" }, - "time": "2022-10-17T18:17:10+00:00" + "time": "2022-12-14T19:25:13+00:00" }, { "name": "brick/math", @@ -376,23 +376,23 @@ }, { "name": "doctrine/dbal", - "version": "3.4.6", + "version": "3.5.1", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "3ce132f7c0b83d33b26ab6ed308e9e9260699bc4" + "reference": "f38ee8aaca2d58ee88653cb34a6a3880c23f38a5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/3ce132f7c0b83d33b26ab6ed308e9e9260699bc4", - "reference": "3ce132f7c0b83d33b26ab6ed308e9e9260699bc4", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/f38ee8aaca2d58ee88653cb34a6a3880c23f38a5", + "reference": "f38ee8aaca2d58ee88653cb34a6a3880c23f38a5", "shasum": "" }, "require": { "composer-runtime-api": "^2", "doctrine/cache": "^1.11|^2.0", "doctrine/deprecations": "^0.5.3|^1", - "doctrine/event-manager": "^1.0", + "doctrine/event-manager": "^1|^2", "php": "^7.4 || ^8.0", "psr/cache": "^1|^2|^3", "psr/log": "^1|^2|^3" @@ -467,7 +467,7 @@ ], "support": { "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/3.4.6" + "source": "https://github.com/doctrine/dbal/tree/3.5.1" }, "funding": [ { @@ -483,7 +483,7 @@ "type": "tidelift" } ], - "time": "2022-10-21T14:38:43+00:00" + "time": "2022-10-24T07:26:18+00:00" }, { "name": "doctrine/deprecations", @@ -1513,16 +1513,16 @@ }, { "name": "laravel/framework", - "version": "v9.41.0", + "version": "v9.43.0", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "cc902ce61b4ca08ca7449664cfab2fa96a1d1e28" + "reference": "011f2e1d49a11c22519a7899b46ddf3bc5b0f40b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/cc902ce61b4ca08ca7449664cfab2fa96a1d1e28", - "reference": "cc902ce61b4ca08ca7449664cfab2fa96a1d1e28", + "url": "https://api.github.com/repos/laravel/framework/zipball/011f2e1d49a11c22519a7899b46ddf3bc5b0f40b", + "reference": "011f2e1d49a11c22519a7899b46ddf3bc5b0f40b", "shasum": "" }, "require": { @@ -1695,7 +1695,7 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2022-11-22T15:10:46+00:00" + "time": "2022-12-06T14:26:07+00:00" }, { "name": "laravel/helpers", @@ -1755,35 +1755,35 @@ }, { "name": "laravel/sanctum", - "version": "v2.15.1", + "version": "v3.0.1", "source": { "type": "git", "url": "https://github.com/laravel/sanctum.git", - "reference": "31fbe6f85aee080c4dc2f9b03dc6dd5d0ee72473" + "reference": "b71e80a3a8e8029e2ec8c1aa814b999609ce16dc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/sanctum/zipball/31fbe6f85aee080c4dc2f9b03dc6dd5d0ee72473", - "reference": "31fbe6f85aee080c4dc2f9b03dc6dd5d0ee72473", + "url": "https://api.github.com/repos/laravel/sanctum/zipball/b71e80a3a8e8029e2ec8c1aa814b999609ce16dc", + "reference": "b71e80a3a8e8029e2ec8c1aa814b999609ce16dc", "shasum": "" }, "require": { "ext-json": "*", - "illuminate/console": "^6.9|^7.0|^8.0|^9.0", - "illuminate/contracts": "^6.9|^7.0|^8.0|^9.0", - "illuminate/database": "^6.9|^7.0|^8.0|^9.0", - "illuminate/support": "^6.9|^7.0|^8.0|^9.0", - "php": "^7.2|^8.0" + "illuminate/console": "^9.21", + "illuminate/contracts": "^9.21", + "illuminate/database": "^9.21", + "illuminate/support": "^9.21", + "php": "^8.0.2" }, "require-dev": { "mockery/mockery": "^1.0", - "orchestra/testbench": "^4.0|^5.0|^6.0|^7.0", - "phpunit/phpunit": "^8.0|^9.3" + "orchestra/testbench": "^7.0", + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.x-dev" + "dev-master": "3.x-dev" }, "laravel": { "providers": [ @@ -1816,7 +1816,7 @@ "issues": "https://github.com/laravel/sanctum/issues", "source": "https://github.com/laravel/sanctum" }, - "time": "2022-04-08T13:39:49+00:00" + "time": "2022-07-29T21:33:30+00:00" }, { "name": "laravel/serializable-closure", @@ -1948,32 +1948,32 @@ }, { "name": "laravel/ui", - "version": "v3.4.6", + "version": "v4.1.1", "source": { "type": "git", "url": "https://github.com/laravel/ui.git", - "reference": "65ec5c03f7fee2c8ecae785795b829a15be48c2c" + "reference": "ac94e596ffd39c63cfa41f5407b765b07df97483" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/ui/zipball/65ec5c03f7fee2c8ecae785795b829a15be48c2c", - "reference": "65ec5c03f7fee2c8ecae785795b829a15be48c2c", + "url": "https://api.github.com/repos/laravel/ui/zipball/ac94e596ffd39c63cfa41f5407b765b07df97483", + "reference": "ac94e596ffd39c63cfa41f5407b765b07df97483", "shasum": "" }, "require": { - "illuminate/console": "^8.42|^9.0", - "illuminate/filesystem": "^8.42|^9.0", - "illuminate/support": "^8.82|^9.0", - "illuminate/validation": "^8.42|^9.0", - "php": "^7.3|^8.0" + "illuminate/console": "^9.21", + "illuminate/filesystem": "^9.21", + "illuminate/support": "^9.21", + "illuminate/validation": "^9.21", + "php": "^8.0" }, "require-dev": { - "orchestra/testbench": "^6.23|^7.0" + "orchestra/testbench": "^7.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.x-dev" + "dev-master": "4.x-dev" }, "laravel": { "providers": [ @@ -2003,9 +2003,9 @@ "ui" ], "support": { - "source": "https://github.com/laravel/ui/tree/v3.4.6" + "source": "https://github.com/laravel/ui/tree/v4.1.1" }, - "time": "2022-05-20T13:38:08+00:00" + "time": "2022-12-05T15:09:21+00:00" }, { "name": "lcobucci/clock", @@ -2144,16 +2144,16 @@ }, { "name": "league/commonmark", - "version": "2.3.7", + "version": "2.3.8", "source": { "type": "git", "url": "https://github.com/thephpleague/commonmark.git", - "reference": "a36bd2be4f5387c0f3a8792a0d76b7d68865abbf" + "reference": "c493585c130544c4e91d2e0e131e6d35cb0cbc47" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/a36bd2be4f5387c0f3a8792a0d76b7d68865abbf", - "reference": "a36bd2be4f5387c0f3a8792a0d76b7d68865abbf", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/c493585c130544c4e91d2e0e131e6d35cb0cbc47", + "reference": "c493585c130544c4e91d2e0e131e6d35cb0cbc47", "shasum": "" }, "require": { @@ -2181,7 +2181,7 @@ "symfony/finder": "^5.3 | ^6.0", "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0", "unleashedtech/php-coding-standard": "^3.1.1", - "vimeo/psalm": "^4.24.0" + "vimeo/psalm": "^4.24.0 || ^5.0.0" }, "suggest": { "symfony/yaml": "v2.3+ required if using the Front Matter extension" @@ -2246,20 +2246,20 @@ "type": "tidelift" } ], - "time": "2022-11-03T17:29:46+00:00" + "time": "2022-12-10T16:02:17+00:00" }, { "name": "league/config", - "version": "v1.1.1", + "version": "v1.2.0", "source": { "type": "git", "url": "https://github.com/thephpleague/config.git", - "reference": "a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e" + "reference": "754b3604fb2984c71f4af4a9cbe7b57f346ec1f3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/config/zipball/a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e", - "reference": "a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e", + "url": "https://api.github.com/repos/thephpleague/config/zipball/754b3604fb2984c71f4af4a9cbe7b57f346ec1f3", + "reference": "754b3604fb2984c71f4af4a9cbe7b57f346ec1f3", "shasum": "" }, "require": { @@ -2268,7 +2268,7 @@ "php": "^7.4 || ^8.0" }, "require-dev": { - "phpstan/phpstan": "^0.12.90", + "phpstan/phpstan": "^1.8.2", "phpunit/phpunit": "^9.5.5", "scrutinizer/ocular": "^1.8.1", "unleashedtech/php-coding-standard": "^3.1", @@ -2328,20 +2328,20 @@ "type": "github" } ], - "time": "2021-08-14T12:15:32+00:00" + "time": "2022-12-11T20:36:23+00:00" }, { "name": "league/flysystem", - "version": "3.10.4", + "version": "3.11.0", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "a7790f3dd1b27af81d380e6b2afa77c16ab7e181" + "reference": "7e423e5dd240a60adfab9bde058d7668863b7731" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/a7790f3dd1b27af81d380e6b2afa77c16ab7e181", - "reference": "a7790f3dd1b27af81d380e6b2afa77c16ab7e181", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/7e423e5dd240a60adfab9bde058d7668863b7731", + "reference": "7e423e5dd240a60adfab9bde058d7668863b7731", "shasum": "" }, "require": { @@ -2403,7 +2403,7 @@ ], "support": { "issues": "https://github.com/thephpleague/flysystem/issues", - "source": "https://github.com/thephpleague/flysystem/tree/3.10.4" + "source": "https://github.com/thephpleague/flysystem/tree/3.11.0" }, "funding": [ { @@ -2419,25 +2419,25 @@ "type": "tidelift" } ], - "time": "2022-11-26T19:48:01+00:00" + "time": "2022-12-02T14:39:57+00:00" }, { "name": "league/flysystem-aws-s3-v3", - "version": "3.5.0", + "version": "3.10.3", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem-aws-s3-v3.git", - "reference": "adb6633f325c934c15a099c363dc5362bdcb07a2" + "reference": "f593bf91f94f2adf4f71513d29f1dfa693f2f640" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/adb6633f325c934c15a099c363dc5362bdcb07a2", - "reference": "adb6633f325c934c15a099c363dc5362bdcb07a2", + "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/f593bf91f94f2adf4f71513d29f1dfa693f2f640", + "reference": "f593bf91f94f2adf4f71513d29f1dfa693f2f640", "shasum": "" }, "require": { "aws/aws-sdk-php": "^3.132.4", - "league/flysystem": "^3.0.0", + "league/flysystem": "^3.10.0", "league/mime-type-detection": "^1.0.0", "php": "^8.0.2" }, @@ -2473,7 +2473,7 @@ ], "support": { "issues": "https://github.com/thephpleague/flysystem-aws-s3-v3/issues", - "source": "https://github.com/thephpleague/flysystem-aws-s3-v3/tree/3.5.0" + "source": "https://github.com/thephpleague/flysystem-aws-s3-v3/tree/3.10.3" }, "funding": [ { @@ -2489,20 +2489,20 @@ "type": "tidelift" } ], - "time": "2022-09-17T21:00:35+00:00" + "time": "2022-10-26T18:15:09+00:00" }, { "name": "league/flysystem-memory", - "version": "3.3.0", + "version": "3.10.3", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem-memory.git", - "reference": "d2a80fbfd3337fac39eeff64c6c0820b6a4902ce" + "reference": "5405162ac81f4de5aa5fa01aae7d07382b7c797b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem-memory/zipball/d2a80fbfd3337fac39eeff64c6c0820b6a4902ce", - "reference": "d2a80fbfd3337fac39eeff64c6c0820b6a4902ce", + "url": "https://api.github.com/repos/thephpleague/flysystem-memory/zipball/5405162ac81f4de5aa5fa01aae7d07382b7c797b", + "reference": "5405162ac81f4de5aa5fa01aae7d07382b7c797b", "shasum": "" }, "require": { @@ -2536,7 +2536,7 @@ ], "support": { "issues": "https://github.com/thephpleague/flysystem-memory/issues", - "source": "https://github.com/thephpleague/flysystem-memory/tree/3.3.0" + "source": "https://github.com/thephpleague/flysystem-memory/tree/3.10.3" }, "funding": [ { @@ -2552,7 +2552,7 @@ "type": "tidelift" } ], - "time": "2022-09-09T10:03:42+00:00" + "time": "2022-10-26T18:30:26+00:00" }, { "name": "league/fractal", @@ -2893,16 +2893,16 @@ }, { "name": "nesbot/carbon", - "version": "2.63.0", + "version": "2.64.0", "source": { "type": "git", "url": "https://github.com/briannesbitt/Carbon.git", - "reference": "ad35dd71a6a212b98e4b87e97389b6fa85f0e347" + "reference": "889546413c97de2d05063b8cb7b193c2531ea211" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/ad35dd71a6a212b98e4b87e97389b6fa85f0e347", - "reference": "ad35dd71a6a212b98e4b87e97389b6fa85f0e347", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/889546413c97de2d05063b8cb7b193c2531ea211", + "reference": "889546413c97de2d05063b8cb7b193c2531ea211", "shasum": "" }, "require": { @@ -2913,7 +2913,7 @@ "symfony/translation": "^3.4 || ^4.0 || ^5.0 || ^6.0" }, "require-dev": { - "doctrine/dbal": "^2.0 || ^3.0", + "doctrine/dbal": "^2.0 || ^3.1.4", "doctrine/orm": "^2.7", "friendsofphp/php-cs-fixer": "^3.0", "kylekatarnls/multi-tester": "^2.0", @@ -2991,7 +2991,7 @@ "type": "tidelift" } ], - "time": "2022-10-30T18:34:28+00:00" + "time": "2022-11-26T17:36:00+00:00" }, { "name": "nette/schema", @@ -3707,75 +3707,6 @@ ], "time": "2022-10-11T16:52:29+00:00" }, - { - "name": "prologue/alerts", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/prologuephp/alerts.git", - "reference": "b2880e28814b8dba8768e60e00511627381efe14" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/prologuephp/alerts/zipball/b2880e28814b8dba8768e60e00511627381efe14", - "reference": "b2880e28814b8dba8768e60e00511627381efe14", - "shasum": "" - }, - "require": { - "illuminate/config": "~9", - "illuminate/session": "~9", - "illuminate/support": "~9" - }, - "require-dev": { - "mockery/mockery": "~0.9", - "phpunit/phpunit": "~4.1" - }, - "type": "library", - "extra": { - "laravel": { - "providers": [ - "Prologue\\Alerts\\AlertsServiceProvider" - ], - "aliases": { - "Alert": "Prologue\\Alerts\\Facades\\Alert" - } - } - }, - "autoload": { - "psr-4": { - "Prologue\\Alerts\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Dries Vints", - "email": "dries.vints@gmail.com", - "homepage": "http://driesvints.com", - "role": "Creator" - }, - { - "name": "Cristian Tabacitu", - "email": "hello@tabacitu.ro", - "homepage": "http://tabacitu.ro", - "role": "Maintainer" - } - ], - "description": "Prologue Alerts is a package that handles global site messages.", - "keywords": [ - "alerts", - "laravel", - "messages" - ], - "support": { - "issues": "https://github.com/prologuephp/alerts/issues", - "source": "https://github.com/prologuephp/alerts/tree/1.0.0" - }, - "time": "2022-01-19T09:28:45+00:00" - }, { "name": "psr/cache", "version": "3.0.0", @@ -4785,16 +4716,16 @@ }, { "name": "spatie/laravel-query-builder", - "version": "5.0.3", + "version": "5.1.1", "source": { "type": "git", "url": "https://github.com/spatie/laravel-query-builder.git", - "reference": "2243e3d60fc184ef20ad2b19765bc7006fa9dbfc" + "reference": "14a6802cd513cfd2abf68044cca5fd7391eb543d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-query-builder/zipball/2243e3d60fc184ef20ad2b19765bc7006fa9dbfc", - "reference": "2243e3d60fc184ef20ad2b19765bc7006fa9dbfc", + "url": "https://api.github.com/repos/spatie/laravel-query-builder/zipball/14a6802cd513cfd2abf68044cca5fd7391eb543d", + "reference": "14a6802cd513cfd2abf68044cca5fd7391eb543d", "shasum": "" }, "require": { @@ -4853,7 +4784,7 @@ "type": "custom" } ], - "time": "2022-07-29T14:19:59+00:00" + "time": "2022-12-02T21:28:40+00:00" }, { "name": "staudenmeir/belongs-to-through", @@ -7499,28 +7430,27 @@ }, { "name": "symfony/yaml", - "version": "v5.4.16", + "version": "v6.0.16", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "ebd37c71f62d5ec5f6e27de3e06fee492d4c6298" + "reference": "eb85bd1b0b297e976f3ada52ad239ef80b4dbd0b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/ebd37c71f62d5ec5f6e27de3e06fee492d4c6298", - "reference": "ebd37c71f62d5ec5f6e27de3e06fee492d4c6298", + "url": "https://api.github.com/repos/symfony/yaml/zipball/eb85bd1b0b297e976f3ada52ad239ef80b4dbd0b", + "reference": "eb85bd1b0b297e976f3ada52ad239ef80b4dbd0b", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", + "php": ">=8.0.2", "symfony/polyfill-ctype": "^1.8" }, "conflict": { - "symfony/console": "<5.3" + "symfony/console": "<5.4" }, "require-dev": { - "symfony/console": "^5.3|^6.0" + "symfony/console": "^5.4|^6.0" }, "suggest": { "symfony/console": "For validating YAML files using the lint command" @@ -7554,7 +7484,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v5.4.16" + "source": "https://github.com/symfony/yaml/tree/v6.0.16" }, "funding": [ { @@ -7570,7 +7500,7 @@ "type": "tidelift" } ], - "time": "2022-11-25T16:04:03+00:00" + "time": "2022-11-25T18:58:46+00:00" }, { "name": "tijsverkoyen/css-to-inline-styles", @@ -8209,32 +8139,35 @@ }, { "name": "doctrine/annotations", - "version": "1.13.3", + "version": "1.14.1", "source": { "type": "git", "url": "https://github.com/doctrine/annotations.git", - "reference": "648b0343343565c4a056bfc8392201385e8d89f0" + "reference": "9e034d7a70032d422169f27d8759e8d84abb4f51" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/annotations/zipball/648b0343343565c4a056bfc8392201385e8d89f0", - "reference": "648b0343343565c4a056bfc8392201385e8d89f0", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/9e034d7a70032d422169f27d8759e8d84abb4f51", + "reference": "9e034d7a70032d422169f27d8759e8d84abb4f51", "shasum": "" }, "require": { - "doctrine/lexer": "1.*", + "doctrine/lexer": "^1 || ^2", "ext-tokenizer": "*", "php": "^7.1 || ^8.0", "psr/cache": "^1 || ^2 || ^3" }, "require-dev": { "doctrine/cache": "^1.11 || ^2.0", - "doctrine/coding-standard": "^6.0 || ^8.1", - "phpstan/phpstan": "^1.4.10 || ^1.8.0", - "phpunit/phpunit": "^7.5 || ^8.0 || ^9.1.5", - "symfony/cache": "^4.4 || ^5.2", + "doctrine/coding-standard": "^9 || ^10", + "phpstan/phpstan": "~1.4.10 || ^1.8.0", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "symfony/cache": "^4.4 || ^5.4 || ^6", "vimeo/psalm": "^4.10" }, + "suggest": { + "php": "PHP 8.0 or higher comes with attributes, a native replacement for annotations" + }, "type": "library", "autoload": { "psr-4": { @@ -8276,9 +8209,9 @@ ], "support": { "issues": "https://github.com/doctrine/annotations/issues", - "source": "https://github.com/doctrine/annotations/tree/1.13.3" + "source": "https://github.com/doctrine/annotations/tree/1.14.1" }, - "time": "2022-07-02T10:48:51+00:00" + "time": "2022-12-12T12:46:12+00:00" }, { "name": "doctrine/instantiator", @@ -8352,20 +8285,20 @@ }, { "name": "fakerphp/faker", - "version": "v1.20.0", + "version": "v1.21.0", "source": { "type": "git", "url": "https://github.com/FakerPHP/Faker.git", - "reference": "37f751c67a5372d4e26353bd9384bc03744ec77b" + "reference": "92efad6a967f0b79c499705c69b662f738cc9e4d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/37f751c67a5372d4e26353bd9384bc03744ec77b", - "reference": "37f751c67a5372d4e26353bd9384bc03744ec77b", + "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/92efad6a967f0b79c499705c69b662f738cc9e4d", + "reference": "92efad6a967f0b79c499705c69b662f738cc9e4d", "shasum": "" }, "require": { - "php": "^7.1 || ^8.0", + "php": "^7.4 || ^8.0", "psr/container": "^1.0 || ^2.0", "symfony/deprecation-contracts": "^2.2 || ^3.0" }, @@ -8376,7 +8309,8 @@ "bamarni/composer-bin-plugin": "^1.4.1", "doctrine/persistence": "^1.3 || ^2.0", "ext-intl": "*", - "symfony/phpunit-bridge": "^4.4 || ^5.2" + "phpunit/phpunit": "^9.5.26", + "symfony/phpunit-bridge": "^5.4.16" }, "suggest": { "doctrine/orm": "Required to use Faker\\ORM\\Doctrine", @@ -8388,7 +8322,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "v1.20-dev" + "dev-main": "v1.21-dev" } }, "autoload": { @@ -8413,9 +8347,9 @@ ], "support": { "issues": "https://github.com/FakerPHP/Faker/issues", - "source": "https://github.com/FakerPHP/Faker/tree/v1.20.0" + "source": "https://github.com/FakerPHP/Faker/tree/v1.21.0" }, - "time": "2022-07-20T13:12:54+00:00" + "time": "2022-12-13T13:54:32+00:00" }, { "name": "filp/whoops", @@ -8630,16 +8564,16 @@ }, { "name": "itsgoingd/clockwork", - "version": "v5.1.11", + "version": "v5.1.12", "source": { "type": "git", "url": "https://github.com/itsgoingd/clockwork.git", - "reference": "a790200347f0c6d07e2fca252ccb446df87520c6" + "reference": "c9dbdbb1f0efd19bb80f1080ef63f1b9b1bc3b1b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/itsgoingd/clockwork/zipball/a790200347f0c6d07e2fca252ccb446df87520c6", - "reference": "a790200347f0c6d07e2fca252ccb446df87520c6", + "url": "https://api.github.com/repos/itsgoingd/clockwork/zipball/c9dbdbb1f0efd19bb80f1080ef63f1b9b1bc3b1b", + "reference": "c9dbdbb1f0efd19bb80f1080ef63f1b9b1bc3b1b", "shasum": "" }, "require": { @@ -8686,7 +8620,7 @@ ], "support": { "issues": "https://github.com/itsgoingd/clockwork/issues", - "source": "https://github.com/itsgoingd/clockwork/tree/v5.1.11" + "source": "https://github.com/itsgoingd/clockwork/tree/v5.1.12" }, "funding": [ { @@ -8694,20 +8628,20 @@ "type": "github" } ], - "time": "2022-11-02T21:11:04+00:00" + "time": "2022-12-13T00:04:12+00:00" }, { "name": "laravel/sail", - "version": "v1.16.3", + "version": "v1.16.4", "source": { "type": "git", "url": "https://github.com/laravel/sail.git", - "reference": "0dbee8802e17911afbe29a8506316343829b056e" + "reference": "72412b14d6f4e73b71b5f3068bdb064184fbb001" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/sail/zipball/0dbee8802e17911afbe29a8506316343829b056e", - "reference": "0dbee8802e17911afbe29a8506316343829b056e", + "url": "https://api.github.com/repos/laravel/sail/zipball/72412b14d6f4e73b71b5f3068bdb064184fbb001", + "reference": "72412b14d6f4e73b71b5f3068bdb064184fbb001", "shasum": "" }, "require": { @@ -8754,7 +8688,7 @@ "issues": "https://github.com/laravel/sail/issues", "source": "https://github.com/laravel/sail" }, - "time": "2022-11-21T16:19:18+00:00" + "time": "2022-12-12T16:47:37+00:00" }, { "name": "mockery/mockery", @@ -9565,16 +9499,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.9.2", + "version": "1.9.3", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "d6fdf01c53978b6429f1393ba4afeca39cc68afa" + "reference": "709999b91448d4f2bb07daffffedc889b33e461c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/d6fdf01c53978b6429f1393ba4afeca39cc68afa", - "reference": "d6fdf01c53978b6429f1393ba4afeca39cc68afa", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/709999b91448d4f2bb07daffffedc889b33e461c", + "reference": "709999b91448d4f2bb07daffffedc889b33e461c", "shasum": "" }, "require": { @@ -9604,7 +9538,7 @@ ], "support": { "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.9.2" + "source": "https://github.com/phpstan/phpstan/tree/1.9.3" }, "funding": [ { @@ -9620,20 +9554,20 @@ "type": "tidelift" } ], - "time": "2022-11-10T09:56:11+00:00" + "time": "2022-12-13T10:28:10+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "9.2.19", + "version": "9.2.21", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "c77b56b63e3d2031bd8997fcec43c1925ae46559" + "reference": "3f893e19712bb0c8bc86665d1562e9fd509c4ef0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/c77b56b63e3d2031bd8997fcec43c1925ae46559", - "reference": "c77b56b63e3d2031bd8997fcec43c1925ae46559", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/3f893e19712bb0c8bc86665d1562e9fd509c4ef0", + "reference": "3f893e19712bb0c8bc86665d1562e9fd509c4ef0", "shasum": "" }, "require": { @@ -9689,7 +9623,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.19" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.21" }, "funding": [ { @@ -9697,7 +9631,7 @@ "type": "github" } ], - "time": "2022-11-18T07:47:47+00:00" + "time": "2022-12-14T13:26:54+00:00" }, { "name": "phpunit/php-file-iterator", @@ -9942,16 +9876,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.5.26", + "version": "9.5.27", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "851867efcbb6a1b992ec515c71cdcf20d895e9d2" + "reference": "a2bc7ffdca99f92d959b3f2270529334030bba38" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/851867efcbb6a1b992ec515c71cdcf20d895e9d2", - "reference": "851867efcbb6a1b992ec515c71cdcf20d895e9d2", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a2bc7ffdca99f92d959b3f2270529334030bba38", + "reference": "a2bc7ffdca99f92d959b3f2270529334030bba38", "shasum": "" }, "require": { @@ -10024,7 +9958,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.26" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.27" }, "funding": [ { @@ -10040,7 +9974,7 @@ "type": "tidelift" } ], - "time": "2022-10-28T06:00:21+00:00" + "time": "2022-12-09T07:31:23+00:00" }, { "name": "sebastian/cli-parser", @@ -11214,16 +11148,16 @@ }, { "name": "spatie/laravel-ignition", - "version": "1.6.1", + "version": "1.6.2", "source": { "type": "git", "url": "https://github.com/spatie/laravel-ignition.git", - "reference": "2b79cf6ed40946b64ac6713d7d2da8a9d87f612b" + "reference": "d6e1e1ad93abe280abf41c33f8ea7647dfc0c233" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-ignition/zipball/2b79cf6ed40946b64ac6713d7d2da8a9d87f612b", - "reference": "2b79cf6ed40946b64ac6713d7d2da8a9d87f612b", + "url": "https://api.github.com/repos/spatie/laravel-ignition/zipball/d6e1e1ad93abe280abf41c33f8ea7647dfc0c233", + "reference": "d6e1e1ad93abe280abf41c33f8ea7647dfc0c233", "shasum": "" }, "require": { @@ -11300,7 +11234,7 @@ "type": "github" } ], - "time": "2022-10-26T17:39:54+00:00" + "time": "2022-12-08T15:31:38+00:00" }, { "name": "symfony/filesystem", diff --git a/config/app.php b/config/app.php index 36d89b711a..68bb4132ab 100644 --- a/config/app.php +++ b/config/app.php @@ -202,11 +202,6 @@ Pterodactyl\Providers\RouteServiceProvider::class, Pterodactyl\Providers\RepositoryServiceProvider::class, Pterodactyl\Providers\ViewComposerServiceProvider::class, - - /* - * Additional Dependencies - */ - Prologue\Alerts\AlertsServiceProvider::class, ], /* @@ -221,7 +216,6 @@ */ 'aliases' => Facade::defaultAliases()->merge([ - 'Alert' => Prologue\Alerts\Facades\Alert::class, 'Carbon' => Carbon\Carbon::class, 'JavaScript' => Laracasts\Utilities\JavaScript\JavaScriptFacade::class, diff --git a/config/prologue/alerts.php b/config/prologue/alerts.php deleted file mode 100644 index 16397703a5..0000000000 --- a/config/prologue/alerts.php +++ /dev/null @@ -1,37 +0,0 @@ - [ - 'info', - 'warning', - 'danger', - 'success', - ], - - /* - |-------------------------------------------------------------------------- - | Session Key - |-------------------------------------------------------------------------- - | - | The session key which is used to store flashed messages into the current - | session. This can be changed if it conflicts with another key. - | - */ - - 'session_key' => 'alert_messages', -]; diff --git a/phpstan.neon b/phpstan.neon index 1a394a28fe..9d13c6c6c8 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -10,9 +10,6 @@ parameters: level: 4 ignoreErrors: - # Ignore dynamic methods from 3rd Party Vendor - - '#Call to an undefined method Prologue\\Alerts\\AlertsMessageBag::(success|info|warning|danger)\(\)#' - # Ignore repository interface missing methods - '#Call to an undefined method Pterodactyl\\Repositories\\Wings\\DaemonRepository::(\w+)\(\)#' @@ -22,9 +19,6 @@ parameters: excludePaths: - app/Repositories - # Bug in Laravel Framework #44807 - - app/Console/Commands/Overrides/UpCommand.php - # More magic spatie to be replaced - app/Extensions/Spatie/Fractalistic/Fractal.php From 0c5416ee27d7836afa07b3ad7eb36267136b6fcf Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Wed, 14 Dec 2022 18:43:20 -0700 Subject: [PATCH 342/458] cli: fix up command override --- app/Console/Commands/Overrides/UpCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Console/Commands/Overrides/UpCommand.php b/app/Console/Commands/Overrides/UpCommand.php index 225634dc21..0a7caaeb77 100644 --- a/app/Console/Commands/Overrides/UpCommand.php +++ b/app/Console/Commands/Overrides/UpCommand.php @@ -21,6 +21,6 @@ public function handle(): int return 1; } - return parent::handle() ?? 0; + return parent::handle(); } } From 507a802dec76ffddf6e20f235166ddc3031d0cd5 Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Wed, 14 Dec 2022 18:53:31 -0700 Subject: [PATCH 343/458] database-host: reverse node relationship --- app/Models/DatabaseHost.php | 19 ++++++++----------- .../Application/DatabaseHostTransformer.php | 1 - .../Database/DatabaseAuthorizationTest.php | 2 +- .../DatabaseManagementServiceTest.php | 10 +++++----- .../DeployServerDatabaseServiceTest.php | 8 ++++---- 5 files changed, 18 insertions(+), 22 deletions(-) diff --git a/app/Models/DatabaseHost.php b/app/Models/DatabaseHost.php index df56eb7c72..19a46333e1 100644 --- a/app/Models/DatabaseHost.php +++ b/app/Models/DatabaseHost.php @@ -3,7 +3,7 @@ namespace Pterodactyl\Models; use Illuminate\Database\Eloquent\Relations\HasMany; -use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Database\Eloquent\Relations\BelongsToMany; /** * @property int $id @@ -13,7 +13,6 @@ * @property string $username * @property string $password * @property int|null $max_databases - * @property int|null $node_id * @property \Carbon\CarbonImmutable $created_at * @property \Carbon\CarbonImmutable $updated_at */ @@ -41,7 +40,7 @@ class DatabaseHost extends Model * Fields that are mass assignable. */ protected $fillable = [ - 'name', 'host', 'port', 'username', 'password', 'max_databases', 'node_id', + 'name', 'host', 'port', 'username', 'password', 'max_databases', ]; /** @@ -50,7 +49,6 @@ class DatabaseHost extends Model protected $casts = [ 'id' => 'integer', 'max_databases' => 'integer', - 'node_id' => 'integer', ]; /** @@ -62,22 +60,21 @@ class DatabaseHost extends Model 'port' => 'required|numeric|between:1,65535', 'username' => 'required|string|max:32', 'password' => 'nullable|string', - 'node_id' => 'sometimes|nullable|integer|exists:nodes,id', ]; /** - * Gets the node associated with a database host. + * Gets the databases associated with this host. */ - public function node(): BelongsTo + public function databases(): HasMany { - return $this->belongsTo(Node::class); + return $this->hasMany(Database::class); } /** - * Gets the databases associated with this host. + * Returns the nodes that a database host is assigned to. */ - public function databases(): HasMany + public function nodes(): BelongsToMany { - return $this->hasMany(Database::class); + return $this->belongsToMany(Node::class); } } diff --git a/app/Transformers/Api/Application/DatabaseHostTransformer.php b/app/Transformers/Api/Application/DatabaseHostTransformer.php index c69f9dff37..dfe18c7c41 100644 --- a/app/Transformers/Api/Application/DatabaseHostTransformer.php +++ b/app/Transformers/Api/Application/DatabaseHostTransformer.php @@ -31,7 +31,6 @@ public function transform(DatabaseHost $model): array 'host' => $model->host, 'port' => $model->port, 'username' => $model->username, - 'node_id' => $model->node_id, 'created_at' => self::formatTimestamp($model->created_at), 'updated_at' => self::formatTimestamp($model->updated_at), ]; diff --git a/tests/Integration/Api/Client/Server/Database/DatabaseAuthorizationTest.php b/tests/Integration/Api/Client/Server/Database/DatabaseAuthorizationTest.php index ba15c595ca..b6f3924e0c 100644 --- a/tests/Integration/Api/Client/Server/Database/DatabaseAuthorizationTest.php +++ b/tests/Integration/Api/Client/Server/Database/DatabaseAuthorizationTest.php @@ -24,7 +24,7 @@ public function testAccessToAServersDatabasesIsRestrictedProperly(string $method // And as no access to $server3. $server3 = $this->createServerModel(); - $host = DatabaseHost::factory()->create([]); + $host = DatabaseHost::factory()->create(); // Set the API $user as a subuser of server 2, but with no permissions // to do anything with the databases for that server. diff --git a/tests/Integration/Services/Databases/DatabaseManagementServiceTest.php b/tests/Integration/Services/Databases/DatabaseManagementServiceTest.php index 7e1319b22c..1bb599ef9c 100644 --- a/tests/Integration/Services/Databases/DatabaseManagementServiceTest.php +++ b/tests/Integration/Services/Databases/DatabaseManagementServiceTest.php @@ -58,7 +58,7 @@ public function testExceptionIsThrownIfClientDatabasesAreNotEnabled() public function testDatabaseCannotBeCreatedIfServerHasReachedLimit() { $server = $this->createServerModel(['database_limit' => 2]); - $host = DatabaseHost::factory()->create(['node_id' => $server->node_id]); + $host = DatabaseHost::factory()->create(); Database::factory()->times(2)->create(['server_id' => $server->id, 'database_host_id' => $host->id]); @@ -90,8 +90,8 @@ public function testCreatingDatabaseWithIdenticalNameTriggersAnException() $server = $this->createServerModel(); $name = DatabaseManagementService::generateUniqueDatabaseName('soemthing', $server->id); - $host = DatabaseHost::factory()->create(['node_id' => $server->node_id]); - $host2 = DatabaseHost::factory()->create(['node_id' => $server->node_id]); + $host = DatabaseHost::factory()->create(); + $host2 = DatabaseHost::factory()->create(); Database::factory()->create([ 'database' => $name, 'database_host_id' => $host->id, @@ -119,7 +119,7 @@ public function testServerDatabaseCanBeCreated() $server = $this->createServerModel(); $name = DatabaseManagementService::generateUniqueDatabaseName('soemthing', $server->id); - $host = DatabaseHost::factory()->create(['node_id' => $server->node_id]); + $host = DatabaseHost::factory()->create(); $this->repository->expects('createDatabase')->with($name); @@ -177,7 +177,7 @@ public function testExceptionEncounteredWhileCreatingDatabaseAttemptsToCleanup() $server = $this->createServerModel(); $name = DatabaseManagementService::generateUniqueDatabaseName('soemthing', $server->id); - $host = DatabaseHost::factory()->create(['node_id' => $server->node_id]); + $host = DatabaseHost::factory()->create(); $this->repository->expects('createDatabase')->with($name)->andThrows(new \BadMethodCallException()); $this->repository->expects('dropDatabase')->with($name); diff --git a/tests/Integration/Services/Databases/DeployServerDatabaseServiceTest.php b/tests/Integration/Services/Databases/DeployServerDatabaseServiceTest.php index ec02c13c7d..52b3b6554f 100644 --- a/tests/Integration/Services/Databases/DeployServerDatabaseServiceTest.php +++ b/tests/Integration/Services/Databases/DeployServerDatabaseServiceTest.php @@ -62,7 +62,7 @@ public function testErrorIsThrownIfNoDatabaseHostsExistOnNode() $server = $this->createServerModel(); $node = Node::factory()->create(['location_id' => $server->location->id]); - DatabaseHost::factory()->create(['node_id' => $node->id]); + DatabaseHost::factory()->create(); config()->set('pterodactyl.client_features.databases.allow_random', false); @@ -97,8 +97,8 @@ public function testDatabaseHostOnSameNodeIsPreferred() $server = $this->createServerModel(); $node = Node::factory()->create(['location_id' => $server->location->id]); - DatabaseHost::factory()->create(['node_id' => $node->id]); - $host = DatabaseHost::factory()->create(['node_id' => $server->node_id]); + DatabaseHost::factory()->create(); + $host = DatabaseHost::factory()->create(); $this->managementService->expects('create')->with($server, [ 'database_host_id' => $host->id, @@ -124,7 +124,7 @@ public function testDatabaseHostIsSelectedIfNoSuitableHostExistsOnSameNode() $server = $this->createServerModel(); $node = Node::factory()->create(['location_id' => $server->location->id]); - $host = DatabaseHost::factory()->create(['node_id' => $node->id]); + $host = DatabaseHost::factory()->create(); $this->managementService->expects('create')->with($server, [ 'database_host_id' => $host->id, From 8fff0846a0632905cd6a498624a682979ee72eef Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Wed, 14 Dec 2022 19:13:00 -0700 Subject: [PATCH 344/458] Attempt to fix Fractal object type being null --- .../Serializers/PterodactylSerializer.php | 22 ++++---- .../Spatie/Fractalistic/Fractal.php | 9 ++-- .../{Nests => Eggs}/EggControllerTest.php | 52 ++++++++++++------- 3 files changed, 47 insertions(+), 36 deletions(-) rename tests/Integration/Api/Application/{Nests => Eggs}/EggControllerTest.php (67%) diff --git a/app/Extensions/League/Fractal/Serializers/PterodactylSerializer.php b/app/Extensions/League/Fractal/Serializers/PterodactylSerializer.php index 5b53a5ad70..27b4724e1e 100644 --- a/app/Extensions/League/Fractal/Serializers/PterodactylSerializer.php +++ b/app/Extensions/League/Fractal/Serializers/PterodactylSerializer.php @@ -6,17 +6,6 @@ class PterodactylSerializer extends ArraySerializer { - /** - * Serialize an item. - */ - public function item(?string $resourceKey, array $data): array - { - return [ - 'object' => $resourceKey, - 'attributes' => $data, - ]; - } - /** * Serialize a collection. */ @@ -33,6 +22,17 @@ public function collection(?string $resourceKey, array $data): array ]; } + /** + * Serialize an item. + */ + public function item(?string $resourceKey, array $data): array + { + return [ + 'object' => $resourceKey, + 'attributes' => $data, + ]; + } + /** * Serialize a null resource. */ diff --git a/app/Extensions/Spatie/Fractalistic/Fractal.php b/app/Extensions/Spatie/Fractalistic/Fractal.php index 0c65d6e8e4..7fcd91b466 100644 --- a/app/Extensions/Spatie/Fractalistic/Fractal.php +++ b/app/Extensions/Spatie/Fractalistic/Fractal.php @@ -3,8 +3,8 @@ namespace Pterodactyl\Extensions\Spatie\Fractalistic; use League\Fractal\Scope; -use League\Fractal\TransformerAbstract; use Spatie\Fractal\Fractal as SpatieFractal; +use Pterodactyl\Transformers\Api\Transformer; use Illuminate\Contracts\Pagination\LengthAwarePaginator; use League\Fractal\Pagination\IlluminatePaginatorAdapter; use Pterodactyl\Extensions\League\Fractal\Serializers\PterodactylSerializer; @@ -32,11 +32,8 @@ public function createData(): Scope // If the resource name is not set attempt to pull it off the transformer // itself and set it automatically. - if ( - is_null($this->resourceName) - && $this->transformer instanceof TransformerAbstract - && method_exists($this->transformer, 'getResourceName') - ) { + $class = is_string($this->transformer) ? new $this->transformer() : $this->transformer; + if (is_null($this->resourceName) && $class instanceof Transformer) { $this->resourceName = $this->transformer->getResourceName(); } diff --git a/tests/Integration/Api/Application/Nests/EggControllerTest.php b/tests/Integration/Api/Application/Eggs/EggControllerTest.php similarity index 67% rename from tests/Integration/Api/Application/Nests/EggControllerTest.php rename to tests/Integration/Api/Application/Eggs/EggControllerTest.php index e41b20eb43..58755545c2 100644 --- a/tests/Integration/Api/Application/Nests/EggControllerTest.php +++ b/tests/Integration/Api/Application/Eggs/EggControllerTest.php @@ -1,21 +1,33 @@ repository = $this->app->make(EggRepositoryInterface::class); + } + /** * Test that all the eggs belonging to a given nest can be returned. */ public function testListAllEggsInNest() { - $eggs = Egg::query()->where('nest_id', 1)->get(); + $eggs = $this->repository->findWhere([['nest_id', '=', 1]]); $response = $this->getJson('/api/application/nests/' . $eggs->first()->nest_id . '/eggs'); $response->assertStatus(Response::HTTP_OK); @@ -32,7 +44,6 @@ public function testListAllEggsInNest() 'files' => [], 'startup' => ['done'], 'stop', - 'logs' => [], 'extends', ], ], @@ -44,12 +55,12 @@ public function testListAllEggsInNest() $egg = $eggs->where('id', '=', $datum['attributes']['id'])->first(); $expected = json_encode(Arr::sortRecursive($datum['attributes'])); - $actual = json_encode(Arr::sortRecursive($this->getTransformer(EggTransformer::class)->transform($egg))); + $actual = json_encode(Arr::sortRecursive((new EggTransformer())->transform($egg))); - $this->assertSame( + $this->assertJsonStringEqualsJsonString( $expected, $actual, - 'Unable to find JSON fragment: ' . PHP_EOL . PHP_EOL . "[$expected]" . PHP_EOL . PHP_EOL . 'within' . PHP_EOL . PHP_EOL . "[$actual]." + 'Unable to find JSON fragment: ' . PHP_EOL . PHP_EOL . "[{$expected}]" . PHP_EOL . PHP_EOL . 'within' . PHP_EOL . PHP_EOL . "[{$actual}]." ); } } @@ -59,9 +70,9 @@ public function testListAllEggsInNest() */ public function testReturnSingleEgg() { - $egg = Egg::query()->findOrFail(1); + $egg = $this->repository->find(1); - $response = $this->getJson('/api/application/nests/' . $egg->nest_id . '/eggs/' . $egg->id); + $response = $this->getJson('/api/application/eggs/' . $egg->id); $response->assertStatus(Response::HTTP_OK); $response->assertJsonStructure([ 'object', @@ -72,7 +83,7 @@ public function testReturnSingleEgg() $response->assertJson([ 'object' => 'egg', - 'attributes' => $this->getTransformer(EggTransformer::class)->transform($egg), + 'attributes' => json_decode(json_encode((new EggTransformer())->transform($egg)), true), ], true); } @@ -81,9 +92,9 @@ public function testReturnSingleEgg() */ public function testReturnSingleEggWithRelationships() { - $egg = Egg::query()->findOrFail(1); + $egg = $this->repository->find(1); - $response = $this->getJson('/api/application/nests/' . $egg->nest_id . '/eggs/' . $egg->id . '?include=servers,variables,nest'); + $response = $this->getJson('/api/application/eggs/' . $egg->id . '?include=servers,variables,nest'); $response->assertStatus(Response::HTTP_OK); $response->assertJsonStructure([ 'object', @@ -102,9 +113,7 @@ public function testReturnSingleEggWithRelationships() */ public function testGetMissingEgg() { - $egg = Egg::query()->findOrFail(1); - - $response = $this->getJson('/api/application/nests/' . $egg->nest_id . '/eggs/0'); + $response = $this->getJson('/api/application/eggs/nil'); $this->assertNotFoundJson($response); } @@ -114,10 +123,15 @@ public function testGetMissingEgg() */ public function testErrorReturnedIfNoPermission() { - $egg = Egg::query()->findOrFail(1); - $this->createNewDefaultApiKey($this->getApiUser(), ['r_eggs' => 0]); + $this->markTestSkipped('todo: implement proper admin api key permissions system'); + } - $response = $this->getJson('/api/application/nests/' . $egg->nest_id . '/eggs'); - $this->assertAccessDeniedJson($response); + /** + * Test that a nests's existence is not exposed unless an API key has permission + * to access the resource. + */ + public function testResourceIsNotExposedWithoutPermissions() + { + $this->markTestSkipped('todo: implement proper admin api key permissions system'); } } From 10b2380e9edb83bfe91ed74ac3ff2ec18ceb323f Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Wed, 14 Dec 2022 19:15:19 -0700 Subject: [PATCH 345/458] Make sure to actually update other references as well --- app/Extensions/Spatie/Fractalistic/Fractal.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Extensions/Spatie/Fractalistic/Fractal.php b/app/Extensions/Spatie/Fractalistic/Fractal.php index 7fcd91b466..7af9b5436d 100644 --- a/app/Extensions/Spatie/Fractalistic/Fractal.php +++ b/app/Extensions/Spatie/Fractalistic/Fractal.php @@ -34,7 +34,7 @@ public function createData(): Scope // itself and set it automatically. $class = is_string($this->transformer) ? new $this->transformer() : $this->transformer; if (is_null($this->resourceName) && $class instanceof Transformer) { - $this->resourceName = $this->transformer->getResourceName(); + $this->resourceName = $class->getResourceName(); } return parent::createData(); From 7f669828c60de832618a7c7ae1edaa953e5e650d Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Wed, 14 Dec 2022 19:53:07 -0700 Subject: [PATCH 346/458] tests: more fixes, but stuff is still broken --- app/Models/User.php | 2 +- .../ApplicationApiIntegrationTestCase.php | 2 + .../Application/Eggs/EggControllerTest.php | 9 --- .../Location/LocationControllerTest.php | 6 +- .../Application/Nests/NestControllerTest.php | 8 +-- .../Users/ExternalUserControllerTest.php | 8 +-- .../Application/Users/UserControllerTest.php | 68 ++++++------------- .../Client/ClientApiIntegrationTestCase.php | 4 +- .../DatabaseManagementServiceTest.php | 6 +- .../DeployServerDatabaseServiceTest.php | 8 +-- 10 files changed, 36 insertions(+), 85 deletions(-) diff --git a/app/Models/User.php b/app/Models/User.php index fa6ebffa16..afdbf65661 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -224,7 +224,7 @@ public function setUsernameAttribute(string $value) public function avatarURL(): string { - return 'https://www.gravatar.com/avatar/' . md5($this->email) . '.jpg'; + return 'https://www.gravatar.com/avatar/' . $this->md5 . '.jpg'; } /** diff --git a/tests/Integration/Api/Application/ApplicationApiIntegrationTestCase.php b/tests/Integration/Api/Application/ApplicationApiIntegrationTestCase.php index 53d902e23b..1c7e7e0016 100644 --- a/tests/Integration/Api/Application/ApplicationApiIntegrationTestCase.php +++ b/tests/Integration/Api/Application/ApplicationApiIntegrationTestCase.php @@ -91,6 +91,8 @@ protected function createApiKey(User $user, array $permissions = []): ApiKey /** * Return a transformer that can be used for testing purposes. + * + * @deprecated Instantiate the transformer directly. */ protected function getTransformer(string $abstract): Transformer { diff --git a/tests/Integration/Api/Application/Eggs/EggControllerTest.php b/tests/Integration/Api/Application/Eggs/EggControllerTest.php index 58755545c2..0fe816127f 100644 --- a/tests/Integration/Api/Application/Eggs/EggControllerTest.php +++ b/tests/Integration/Api/Application/Eggs/EggControllerTest.php @@ -125,13 +125,4 @@ public function testErrorReturnedIfNoPermission() { $this->markTestSkipped('todo: implement proper admin api key permissions system'); } - - /** - * Test that a nests's existence is not exposed unless an API key has permission - * to access the resource. - */ - public function testResourceIsNotExposedWithoutPermissions() - { - $this->markTestSkipped('todo: implement proper admin api key permissions system'); - } } diff --git a/tests/Integration/Api/Application/Location/LocationControllerTest.php b/tests/Integration/Api/Application/Location/LocationControllerTest.php index 7a02092b19..92081b5e13 100644 --- a/tests/Integration/Api/Application/Location/LocationControllerTest.php +++ b/tests/Integration/Api/Application/Location/LocationControllerTest.php @@ -259,10 +259,6 @@ public function testGetMissingLocation() */ public function testErrorReturnedIfNoPermission() { - $location = Location::factory()->create(); - $this->createNewDefaultApiKey($this->getApiUser(), ['r_locations' => 0]); - - $response = $this->getJson('/api/application/locations/' . $location->id); - $this->assertAccessDeniedJson($response); + $this->markTestSkipped('todo: implement proper admin api key permissions system'); } } diff --git a/tests/Integration/Api/Application/Nests/NestControllerTest.php b/tests/Integration/Api/Application/Nests/NestControllerTest.php index 5cbed783c2..7b0e68fa48 100644 --- a/tests/Integration/Api/Application/Nests/NestControllerTest.php +++ b/tests/Integration/Api/Application/Nests/NestControllerTest.php @@ -45,7 +45,7 @@ public function testNestResponse() 'pagination' => [ 'total' => 4, 'count' => 4, - 'per_page' => 50, + 'per_page' => 10, 'current_page' => 1, 'total_pages' => 1, ], @@ -118,10 +118,6 @@ public function testGetMissingNest() */ public function testErrorReturnedIfNoPermission() { - $nest = $this->repository->find(1); - $this->createNewDefaultApiKey($this->getApiUser(), ['r_nests' => 0]); - - $response = $this->getJson('/api/application/nests/' . $nest->id); - $this->assertAccessDeniedJson($response); + $this->markTestSkipped('todo: implement proper admin api key permissions system'); } } diff --git a/tests/Integration/Api/Application/Users/ExternalUserControllerTest.php b/tests/Integration/Api/Application/Users/ExternalUserControllerTest.php index 053ba36eea..4685448398 100644 --- a/tests/Integration/Api/Application/Users/ExternalUserControllerTest.php +++ b/tests/Integration/Api/Application/Users/ExternalUserControllerTest.php @@ -37,7 +37,7 @@ public function testGetRemoteUser() 'email' => $user->email, 'language' => $user->language, 'root_admin' => (bool) $user->root_admin, - '2fa' => (bool) $user->totp_enabled, + '2fa' => (bool) $user->use_totp, 'created_at' => $this->formatTimestamp($user->created_at), 'updated_at' => $this->formatTimestamp($user->updated_at), ], @@ -59,10 +59,6 @@ public function testGetMissingUser() */ public function testErrorReturnedIfNoPermission() { - $user = User::factory()->create(['external_id' => Str::random()]); - $this->createNewDefaultApiKey($this->getApiUser(), ['r_users' => 0]); - - $response = $this->getJson('/api/application/users/external/' . $user->external_id); - $this->assertAccessDeniedJson($response); + $this->markTestSkipped('todo: implement proper admin api key permissions system'); } } diff --git a/tests/Integration/Api/Application/Users/UserControllerTest.php b/tests/Integration/Api/Application/Users/UserControllerTest.php index 60f104201b..43cc83f5ab 100644 --- a/tests/Integration/Api/Application/Users/UserControllerTest.php +++ b/tests/Integration/Api/Application/Users/UserControllerTest.php @@ -4,7 +4,6 @@ use Pterodactyl\Models\User; use Illuminate\Http\Response; -use Pterodactyl\Services\Acl\Api\AdminAcl; use Pterodactyl\Transformers\Api\Application\UserTransformer; use Pterodactyl\Transformers\Api\Application\ServerTransformer; use Pterodactyl\Tests\Integration\Api\Application\ApplicationApiIntegrationTestCase; @@ -24,8 +23,8 @@ public function testGetUsers() $response->assertJsonStructure([ 'object', 'data' => [ - ['object', 'attributes' => ['id', 'external_id', 'uuid', 'username', 'email', 'language', 'root_admin', '2fa', 'created_at', 'updated_at']], - ['object', 'attributes' => ['id', 'external_id', 'uuid', 'username', 'email', 'language', 'root_admin', '2fa', 'created_at', 'updated_at']], + ['object', 'attributes' => ['id', 'external_id', 'uuid', 'username', 'email', 'language', 'admin_role_id', 'root_admin', '2fa', 'avatar_url', 'role_name', 'created_at', 'updated_at']], + ['object', 'attributes' => ['id', 'external_id', 'uuid', 'username', 'email', 'language', 'admin_role_id', 'root_admin', '2fa', 'avatar_url', 'role_name', 'created_at', 'updated_at']], ], 'meta' => ['pagination' => ['total', 'count', 'per_page', 'current_page', 'total_pages']], ]); @@ -53,8 +52,11 @@ public function testGetUsers() 'username' => $this->getApiUser()->username, 'email' => $this->getApiUser()->email, 'language' => $this->getApiUser()->language, + 'admin_role_id' => $this->getApiUser()->admin_role_id, 'root_admin' => $this->getApiUser()->root_admin, '2fa' => $this->getApiUser()->use_totp, + 'avatar_url' => $this->getApiUser()->avatarURL(), + 'role_name' => $this->getApiUser()->adminRoleName(), 'created_at' => $this->formatTimestamp($this->getApiUser()->created_at), 'updated_at' => $this->formatTimestamp($this->getApiUser()->updated_at), ], @@ -68,8 +70,11 @@ public function testGetUsers() 'username' => $user->username, 'email' => $user->email, 'language' => $user->language, + 'admin_role_id' => $user->admin_role_id, 'root_admin' => (bool) $user->root_admin, '2fa' => (bool) $user->use_totp, + 'avatar_url' => $user->avatarURL(), + 'role_name' => $user->adminRoleName(), 'created_at' => $this->formatTimestamp($user->created_at), 'updated_at' => $this->formatTimestamp($user->updated_at), ], @@ -88,7 +93,7 @@ public function testGetSingleUser() $response->assertJsonCount(2); $response->assertJsonStructure([ 'object', - 'attributes' => ['id', 'external_id', 'uuid', 'username', 'email', 'language', 'root_admin', '2fa', 'created_at', 'updated_at'], + 'attributes' => ['id', 'external_id', 'uuid', 'username', 'email', 'language', 'admin_role_id', 'root_admin', '2fa', 'avatar_url', 'role_name', 'created_at', 'updated_at'], ]); $response->assertJson([ @@ -100,8 +105,11 @@ public function testGetSingleUser() 'username' => $user->username, 'email' => $user->email, 'language' => $user->language, + 'admin_role_id' => $user->admin_role_id, 'root_admin' => (bool) $user->root_admin, - '2fa' => (bool) $user->totp_enabled, + '2fa' => (bool) $user->use_totp, + 'avatar_url' => $user->avatarURL(), + 'role_name' => $user->adminRoleName(), 'created_at' => $this->formatTimestamp($user->created_at), 'updated_at' => $this->formatTimestamp($user->updated_at), ], @@ -122,7 +130,7 @@ public function testRelationshipsCanBeLoaded() $response->assertJsonStructure([ 'object', 'attributes' => [ - 'id', 'external_id', 'uuid', 'username', 'email', 'language', 'root_admin', '2fa', 'created_at', 'updated_at', + 'id', 'external_id', 'uuid', 'username', 'email', 'language', 'admin_role_id', 'root_admin', '2fa', 'avatar_url', 'role_name', 'created_at', 'updated_at', 'relationships' => ['servers' => ['object', 'data' => [['object', 'attributes' => []]]]], ], ]); @@ -144,33 +152,7 @@ public function testRelationshipsCanBeLoaded() */ public function testKeyWithoutPermissionCannotLoadRelationship() { - $this->createNewDefaultApiKey($this->getApiUser(), ['r_servers' => 0]); - - $user = User::factory()->create(); - $this->createServerModel(['user_id' => $user->id]); - - $response = $this->getJson('/api/application/users/' . $user->id . '?include=servers'); - $response->assertStatus(Response::HTTP_OK); - $response->assertJsonCount(2)->assertJsonCount(1, 'attributes.relationships'); - $response->assertJsonStructure([ - 'attributes' => [ - 'relationships' => [ - 'servers' => ['object', 'attributes'], - ], - ], - ]); - - // Just assert that we see the expected relationship IDs in the response. - $response->assertJson([ - 'attributes' => [ - 'relationships' => [ - 'servers' => [ - 'object' => 'null_resource', - 'attributes' => null, - ], - ], - ], - ]); + $this->markTestSkipped('todo: implement proper admin api key permissions system'); } /** @@ -188,11 +170,7 @@ public function testGetMissingUser() */ public function testErrorReturnedIfNoPermission() { - $user = User::factory()->create(); - $this->createNewDefaultApiKey($this->getApiUser(), ['r_users' => 0]); - - $response = $this->getJson('/api/application/users/' . $user->id); - $this->assertAccessDeniedJson($response); + $this->markTestSkipped('todo: implement proper admin api key permissions system'); } /** @@ -209,7 +187,7 @@ public function testCreateUser() $response->assertJsonCount(3); $response->assertJsonStructure([ 'object', - 'attributes' => ['id', 'external_id', 'uuid', 'username', 'email', 'language', 'root_admin', '2fa', 'created_at', 'updated_at'], + 'attributes' => ['id', 'external_id', 'uuid', 'username', 'email', 'language', 'admin_role_id', 'root_admin', '2fa', 'avatar_url', 'role_name', 'created_at', 'updated_at'], 'meta' => ['resource'], ]); @@ -240,7 +218,7 @@ public function testUpdateUser() $response->assertJsonCount(2); $response->assertJsonStructure([ 'object', - 'attributes' => ['id', 'external_id', 'uuid', 'username', 'email', 'language', 'root_admin', '2fa', 'created_at', 'updated_at'], + 'attributes' => ['id', 'external_id', 'uuid', 'username', 'email', 'language', 'admin_role_id', 'root_admin', '2fa', 'avatar_url', 'role_name', 'created_at', 'updated_at'], ]); $this->assertDatabaseHas('users', ['username' => 'new.test.name', 'email' => 'new@emailtest.com']); @@ -274,15 +252,7 @@ public function testDeleteUser() */ public function testApiKeyWithoutWritePermissions(string $method, string $url) { - $this->createNewDefaultApiKey($this->getApiUser(), ['r_users' => AdminAcl::READ]); - - if (str_contains($url, '{id}')) { - $user = User::factory()->create(); - $url = str_replace('{id}', $user->id, $url); - } - - $response = $this->$method($url); - $this->assertAccessDeniedJson($response); + $this->markTestSkipped('todo: implement proper admin api key permissions system'); } /** diff --git a/tests/Integration/Api/Client/ClientApiIntegrationTestCase.php b/tests/Integration/Api/Client/ClientApiIntegrationTestCase.php index bc515386c7..f5ec4703fb 100644 --- a/tests/Integration/Api/Client/ClientApiIntegrationTestCase.php +++ b/tests/Integration/Api/Client/ClientApiIntegrationTestCase.php @@ -14,10 +14,10 @@ use Illuminate\Support\Collection; use Pterodactyl\Models\Allocation; use Pterodactyl\Models\DatabaseHost; +use Pterodactyl\Transformers\Api\Transformer; use Pterodactyl\Tests\Integration\TestResponse; use Pterodactyl\Tests\Integration\IntegrationTestCase; use Illuminate\Database\Eloquent\Model as EloquentModel; -use Pterodactyl\Transformers\Api\Client\BaseClientTransformer; abstract class ClientApiIntegrationTestCase extends IntegrationTestCase { @@ -89,7 +89,7 @@ protected function assertJsonTransformedWith(array $data, Model|EloquentModel $m $transformer = sprintf('\\Pterodactyl\\Transformers\\Api\\Client\\%sTransformer', $reflect->getShortName()); $transformer = new $transformer(); - $this->assertInstanceOf(BaseClientTransformer::class, $transformer); + $this->assertInstanceOf(Transformer::class, $transformer); $this->assertSame( $transformer->transform($model), diff --git a/tests/Integration/Services/Databases/DatabaseManagementServiceTest.php b/tests/Integration/Services/Databases/DatabaseManagementServiceTest.php index 1bb599ef9c..2848adb0f9 100644 --- a/tests/Integration/Services/Databases/DatabaseManagementServiceTest.php +++ b/tests/Integration/Services/Databases/DatabaseManagementServiceTest.php @@ -88,7 +88,7 @@ public function testEmptyDatabaseNameOrInvalidNameTriggersAnException(array $dat public function testCreatingDatabaseWithIdenticalNameTriggersAnException() { $server = $this->createServerModel(); - $name = DatabaseManagementService::generateUniqueDatabaseName('soemthing', $server->id); + $name = DatabaseManagementService::generateUniqueDatabaseName('something', $server->id); $host = DatabaseHost::factory()->create(); $host2 = DatabaseHost::factory()->create(); @@ -117,7 +117,7 @@ public function testCreatingDatabaseWithIdenticalNameTriggersAnException() public function testServerDatabaseCanBeCreated() { $server = $this->createServerModel(); - $name = DatabaseManagementService::generateUniqueDatabaseName('soemthing', $server->id); + $name = DatabaseManagementService::generateUniqueDatabaseName('something', $server->id); $host = DatabaseHost::factory()->create(); @@ -175,7 +175,7 @@ public function testServerDatabaseCanBeCreated() public function testExceptionEncounteredWhileCreatingDatabaseAttemptsToCleanup() { $server = $this->createServerModel(); - $name = DatabaseManagementService::generateUniqueDatabaseName('soemthing', $server->id); + $name = DatabaseManagementService::generateUniqueDatabaseName('something', $server->id); $host = DatabaseHost::factory()->create(); diff --git a/tests/Integration/Services/Databases/DeployServerDatabaseServiceTest.php b/tests/Integration/Services/Databases/DeployServerDatabaseServiceTest.php index 52b3b6554f..9dada94059 100644 --- a/tests/Integration/Services/Databases/DeployServerDatabaseServiceTest.php +++ b/tests/Integration/Services/Databases/DeployServerDatabaseServiceTest.php @@ -61,8 +61,8 @@ public function testErrorIsThrownIfNoDatabaseHostsExistOnNode() { $server = $this->createServerModel(); + $host = DatabaseHost::factory()->create(); $node = Node::factory()->create(['location_id' => $server->location->id]); - DatabaseHost::factory()->create(); config()->set('pterodactyl.client_features.databases.allow_random', false); @@ -96,9 +96,9 @@ public function testDatabaseHostOnSameNodeIsPreferred() { $server = $this->createServerModel(); - $node = Node::factory()->create(['location_id' => $server->location->id]); - DatabaseHost::factory()->create(); + $node = Node::factory()->create(['location_id' => $server->location->id, 'database_host_id' => DatabaseHost::factory()->create()->id]); $host = DatabaseHost::factory()->create(); + $server->node->database_host_id = $host->id; $this->managementService->expects('create')->with($server, [ 'database_host_id' => $host->id, @@ -123,8 +123,8 @@ public function testDatabaseHostIsSelectedIfNoSuitableHostExistsOnSameNode() { $server = $this->createServerModel(); - $node = Node::factory()->create(['location_id' => $server->location->id]); $host = DatabaseHost::factory()->create(); + $node = Node::factory()->create(['location_id' => $server->location->id, 'database_host_id' => $host->id]); $this->managementService->expects('create')->with($server, [ 'database_host_id' => $host->id, From 926c8563d06386031e26a6be81f3b4d963f59e4a Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Thu, 15 Dec 2022 12:26:34 -0700 Subject: [PATCH 347/458] user: cleanup --- app/Models/User.php | 73 ++++++++----------- .../Api/Application/UserTransformer.php | 4 +- .../Api/Client/UserTransformer.php | 2 +- .../ApplicationApiIntegrationTestCase.php | 2 +- .../Application/Users/UserControllerTest.php | 12 +-- 5 files changed, 42 insertions(+), 51 deletions(-) diff --git a/app/Models/User.php b/app/Models/User.php index afdbf65661..6ffe070915 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -41,6 +41,10 @@ * @property bool $gravatar * @property \Illuminate\Support\Carbon|null $created_at * @property \Illuminate\Support\Carbon|null $updated_at + * @property string $avatar_url + * @property string|null $admin_role_name + * @property string $md5 + * @property \Pterodactyl\Models\AdminRole|null $adminRole * @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\ApiKey[] $apiKeys * @property int|null $api_keys_count * @property \Illuminate\Notifications\DatabaseNotificationCollection|\Illuminate\Notifications\DatabaseNotification[] $notifications @@ -127,10 +131,6 @@ class User extends Model implements 'root_admin', ]; - protected $appends = [ - 'md5', - ]; - /** * Cast values to correct type. */ @@ -192,11 +192,9 @@ public static function getRules(): array */ public function toReactObject(): array { - $object = Collection::make($this->toArray())->except(['id', 'external_id'])->toArray(); - $object['avatar_url'] = $this->avatarURL(); - $object['role_name'] = $this->adminRoleName(); - - return $object; + return Collection::make($this->append(['avatar_url', 'admin_role_name'])->toArray()) + ->except(['id', 'external_id', 'admin_role', 'admin_role_id']) + ->toArray(); } /** @@ -222,35 +220,39 @@ public function setUsernameAttribute(string $value) $this->attributes['username'] = mb_strtolower($value); } - public function avatarURL(): string + public function avatarUrl(): Attribute { - return 'https://www.gravatar.com/avatar/' . $this->md5 . '.jpg'; + return Attribute::make( + get: fn () => 'https://www.gravatar.com/avatar/' . $this->md5 . '.jpg', + ); } - /** - * Gets the name of the role assigned to a user. - */ - public function adminRoleName(): ?string + public function adminRoleName(): Attribute { - $role = $this->adminRole; - if (is_null($role)) { - return $this->root_admin ? 'None' : null; - } - - return $role->name; + return Attribute::make( + get: fn () => is_null($this->adminRole) ? ($this->root_admin ? 'None' : null) : $this->adminRole->name, + ); } - public function adminRole(): HasOne + public function md5(): Attribute { - return $this->hasOne(AdminRole::class, 'id', 'admin_role_id'); + return Attribute::make( + get: fn () => md5(strtolower($this->email)), + ); } /** - * Returns all servers that a user owns. + * Returns all the activity logs where this user is the subject — not to + * be confused by activity logs where this user is the _actor_. */ - public function servers(): HasMany + public function activity(): MorphToMany { - return $this->hasMany(Server::class, 'owner_id'); + return $this->morphToMany(ActivityLog::class, 'subject', 'activity_log_subjects'); + } + + public function adminRole(): HasOne + { + return $this->hasOne(AdminRole::class, 'id', 'admin_role_id'); } public function apiKeys(): HasMany @@ -264,25 +266,14 @@ public function recoveryTokens(): HasMany return $this->hasMany(RecoveryToken::class); } - public function sshKeys(): HasMany - { - return $this->hasMany(UserSSHKey::class); - } - - /** - * Returns all the activity logs where this user is the subject — not to - * be confused by activity logs where this user is the _actor_. - */ - public function activity(): MorphToMany + public function servers(): HasMany { - return $this->morphToMany(ActivityLog::class, 'subject', 'activity_log_subjects'); + return $this->hasMany(Server::class, 'owner_id'); } - public function md5(): Attribute + public function sshKeys(): HasMany { - return Attribute::make( - get: fn () => md5(strtolower($this->email)), - ); + return $this->hasMany(UserSSHKey::class); } /** diff --git a/app/Transformers/Api/Application/UserTransformer.php b/app/Transformers/Api/Application/UserTransformer.php index c50a9d8c6e..2a30656541 100644 --- a/app/Transformers/Api/Application/UserTransformer.php +++ b/app/Transformers/Api/Application/UserTransformer.php @@ -37,9 +37,9 @@ public function transform(User $model): array 'language' => $model->language, 'root_admin' => (bool) $model->root_admin, '2fa' => (bool) $model->use_totp, - 'avatar_url' => $model->avatarURL(), + 'avatar_url' => $model->avatar_url, 'admin_role_id' => $model->admin_role_id, - 'role_name' => $model->adminRoleName(), + 'role_name' => $model->admin_role_name, 'created_at' => self::formatTimestamp($model->created_at), 'updated_at' => self::formatTimestamp($model->updated_at), ]; diff --git a/app/Transformers/Api/Client/UserTransformer.php b/app/Transformers/Api/Client/UserTransformer.php index 7a2f077546..c076e6b998 100644 --- a/app/Transformers/Api/Client/UserTransformer.php +++ b/app/Transformers/Api/Client/UserTransformer.php @@ -25,7 +25,7 @@ public function transform(User $model): array 'uuid' => $model->uuid, 'username' => $model->username, 'email' => $model->email, - 'image' => $model->avatarURL(), + 'image' => $model->avatar_url, '2fa_enabled' => $model->use_totp, 'created_at' => self::formatTimestamp($model->created_at), ]; diff --git a/tests/Integration/Api/Application/ApplicationApiIntegrationTestCase.php b/tests/Integration/Api/Application/ApplicationApiIntegrationTestCase.php index 1c7e7e0016..a137a324d4 100644 --- a/tests/Integration/Api/Application/ApplicationApiIntegrationTestCase.php +++ b/tests/Integration/Api/Application/ApplicationApiIntegrationTestCase.php @@ -92,7 +92,7 @@ protected function createApiKey(User $user, array $permissions = []): ApiKey /** * Return a transformer that can be used for testing purposes. * - * @deprecated Instantiate the transformer directly. + * @deprecated instantiate the transformer directly */ protected function getTransformer(string $abstract): Transformer { diff --git a/tests/Integration/Api/Application/Users/UserControllerTest.php b/tests/Integration/Api/Application/Users/UserControllerTest.php index 43cc83f5ab..738f5843f4 100644 --- a/tests/Integration/Api/Application/Users/UserControllerTest.php +++ b/tests/Integration/Api/Application/Users/UserControllerTest.php @@ -55,8 +55,8 @@ public function testGetUsers() 'admin_role_id' => $this->getApiUser()->admin_role_id, 'root_admin' => $this->getApiUser()->root_admin, '2fa' => $this->getApiUser()->use_totp, - 'avatar_url' => $this->getApiUser()->avatarURL(), - 'role_name' => $this->getApiUser()->adminRoleName(), + 'avatar_url' => $this->getApiUser()->avatar_url, + 'role_name' => $this->getApiUser()->admin_role_name, 'created_at' => $this->formatTimestamp($this->getApiUser()->created_at), 'updated_at' => $this->formatTimestamp($this->getApiUser()->updated_at), ], @@ -73,8 +73,8 @@ public function testGetUsers() 'admin_role_id' => $user->admin_role_id, 'root_admin' => (bool) $user->root_admin, '2fa' => (bool) $user->use_totp, - 'avatar_url' => $user->avatarURL(), - 'role_name' => $user->adminRoleName(), + 'avatar_url' => $user->avatar_url, + 'role_name' => $user->admin_role_name, 'created_at' => $this->formatTimestamp($user->created_at), 'updated_at' => $this->formatTimestamp($user->updated_at), ], @@ -108,8 +108,8 @@ public function testGetSingleUser() 'admin_role_id' => $user->admin_role_id, 'root_admin' => (bool) $user->root_admin, '2fa' => (bool) $user->use_totp, - 'avatar_url' => $user->avatarURL(), - 'role_name' => $user->adminRoleName(), + 'avatar_url' => $user->avatar_url, + 'role_name' => $user->admin_role_name, 'created_at' => $this->formatTimestamp($user->created_at), 'updated_at' => $this->formatTimestamp($user->updated_at), ], From d1c7494933b7fa4a216eac2c374176546f4d07b9 Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Thu, 15 Dec 2022 12:39:37 -0700 Subject: [PATCH 348/458] app: fix DeployServerDatabaseService --- .../Databases/DeployServerDatabaseService.php | 19 ++++++++++--------- .../DeployServerDatabaseServiceTest.php | 11 ++++++----- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/app/Services/Databases/DeployServerDatabaseService.php b/app/Services/Databases/DeployServerDatabaseService.php index e22eba51d4..ec678e40ac 100644 --- a/app/Services/Databases/DeployServerDatabaseService.php +++ b/app/Services/Databases/DeployServerDatabaseService.php @@ -27,21 +27,22 @@ public function handle(Server $server, array $data): Database Assert::notEmpty($data['database'] ?? null); Assert::notEmpty($data['remote'] ?? null); - $hosts = DatabaseHost::query()->get()->toBase(); - if ($hosts->isEmpty()) { - throw new NoSuitableDatabaseHostException(); - } else { - $nodeHosts = $hosts->where('node_id', $server->node_id)->toBase(); + $databaseHostId = $server->node->database_host_id; + if (is_null($databaseHostId)) { + if (!config('pterodactyl.client_features.databases.allow_random')) { + throw new NoSuitableDatabaseHostException(); + } - if ($nodeHosts->isEmpty() && !config('pterodactyl.client_features.databases.allow_random')) { + $hosts = DatabaseHost::query()->get()->toBase(); + if ($hosts->isEmpty()) { throw new NoSuitableDatabaseHostException(); } + + $databaseHostId = $hosts->random()->id; } return $this->managementService->create($server, [ - 'database_host_id' => $nodeHosts->isEmpty() - ? $hosts->random()->id - : $nodeHosts->random()->id, + 'database_host_id' => $databaseHostId, 'database' => DatabaseManagementService::generateUniqueDatabaseName($data['database'], $server->id), 'remote' => $data['remote'], ]); diff --git a/tests/Integration/Services/Databases/DeployServerDatabaseServiceTest.php b/tests/Integration/Services/Databases/DeployServerDatabaseServiceTest.php index 9dada94059..f1511852b5 100644 --- a/tests/Integration/Services/Databases/DeployServerDatabaseServiceTest.php +++ b/tests/Integration/Services/Databases/DeployServerDatabaseServiceTest.php @@ -62,7 +62,7 @@ public function testErrorIsThrownIfNoDatabaseHostsExistOnNode() $server = $this->createServerModel(); $host = DatabaseHost::factory()->create(); - $node = Node::factory()->create(['location_id' => $server->location->id]); + $node = Node::factory()->create(['database_host_id' => $host->id, 'location_id' => $server->location->id]); config()->set('pterodactyl.client_features.databases.allow_random', false); @@ -96,12 +96,13 @@ public function testDatabaseHostOnSameNodeIsPreferred() { $server = $this->createServerModel(); - $node = Node::factory()->create(['location_id' => $server->location->id, 'database_host_id' => DatabaseHost::factory()->create()->id]); - $host = DatabaseHost::factory()->create(); - $server->node->database_host_id = $host->id; + $host1 = DatabaseHost::factory()->create(); + $host2 = DatabaseHost::factory()->create(); + $node = Node::factory()->create(['database_host_id' => $host2->id, 'location_id' => $server->location->id]); + $server->node->database_host_id = $host2->id; $this->managementService->expects('create')->with($server, [ - 'database_host_id' => $host->id, + 'database_host_id' => $host2->id, 'database' => "s{$server->id}_something", 'remote' => '%', ])->andReturns(new Database()); From 5402584508083bc016618ca0dbd7310ff27a52b6 Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Thu, 15 Dec 2022 19:06:14 -0700 Subject: [PATCH 349/458] ui(admin): add "working" React admin ui --- app/Http/Kernel.php | 2 + .../SubstituteApplicationApiBindings.php | 66 ++++ package.json | 4 +- .../api/admin/databases/createDatabase.ts | 12 + .../api/admin/databases/deleteDatabase.ts | 9 + .../api/admin/databases/getDatabase.ts | 10 + .../api/admin/databases/getDatabases.ts | 64 ++++ .../api/admin/databases/searchDatabases.ts | 25 ++ .../api/admin/databases/updateDatabase.ts | 12 + resources/scripts/api/admin/egg.ts | 104 ++++++ resources/scripts/api/admin/eggs/createEgg.ts | 31 ++ .../api/admin/eggs/createEggVariable.ts | 22 ++ resources/scripts/api/admin/eggs/deleteEgg.ts | 9 + .../api/admin/eggs/deleteEggVariable.ts | 9 + resources/scripts/api/admin/eggs/getEgg.ts | 108 ++++++ resources/scripts/api/admin/eggs/updateEgg.ts | 31 ++ .../api/admin/eggs/updateEggVariables.ts | 21 ++ resources/scripts/api/admin/getVersion.ts | 22 ++ resources/scripts/api/admin/index.ts | 66 ++++ resources/scripts/api/admin/location.ts | 13 + .../api/admin/locations/createLocation.ts | 12 + .../api/admin/locations/deleteLocation.ts | 9 + .../api/admin/locations/getLocation.ts | 10 + .../api/admin/locations/getLocations.ts | 54 +++ .../api/admin/locations/searchLocations.ts | 25 ++ .../api/admin/locations/updateLocation.ts | 12 + .../scripts/api/admin/mounts/createMount.ts | 12 + .../scripts/api/admin/mounts/deleteMount.ts | 9 + .../scripts/api/admin/mounts/getMount.ts | 10 + .../scripts/api/admin/mounts/getMounts.ts | 80 ++++ .../scripts/api/admin/mounts/updateMount.ts | 12 + resources/scripts/api/admin/nest.ts | 25 ++ .../scripts/api/admin/nests/createNest.ts | 12 + .../scripts/api/admin/nests/deleteNest.ts | 9 + resources/scripts/api/admin/nests/getEggs.ts | 38 ++ resources/scripts/api/admin/nests/getNest.ts | 10 + resources/scripts/api/admin/nests/getNests.ts | 66 ++++ .../scripts/api/admin/nests/importEgg.ts | 17 + .../scripts/api/admin/nests/updateNest.ts | 12 + resources/scripts/api/admin/node.ts | 84 +++++ .../nodes/allocations/createAllocation.ts | 16 + .../nodes/allocations/deleteAllocation.ts | 9 + .../admin/nodes/allocations/getAllocations.ts | 39 ++ .../scripts/api/admin/nodes/createNode.ts | 42 +++ .../scripts/api/admin/nodes/deleteNode.ts | 9 + .../scripts/api/admin/nodes/getAllocations.ts | 61 +++ resources/scripts/api/admin/nodes/getNode.ts | 10 + .../api/admin/nodes/getNodeConfiguration.ts | 9 + .../api/admin/nodes/getNodeInformation.ts | 19 + resources/scripts/api/admin/nodes/getNodes.ts | 107 ++++++ .../scripts/api/admin/nodes/updateNode.ts | 21 ++ resources/scripts/api/admin/roles.ts | 103 ++++++ resources/scripts/api/admin/server.ts | 99 +++++ .../scripts/api/admin/servers/createServer.ts | 80 ++++ .../scripts/api/admin/servers/deleteServer.ts | 9 + .../scripts/api/admin/servers/getServer.ts | 10 + .../scripts/api/admin/servers/getServers.ts | 177 +++++++++ .../scripts/api/admin/servers/updateServer.ts | 64 ++++ .../api/admin/servers/updateServerStartup.ts | 28 ++ resources/scripts/api/admin/users.ts | 96 +++++ .../scripts/api/definitions/admin/index.ts | 2 + .../scripts/api/definitions/admin/models.d.ts | 29 ++ .../api/definitions/admin/transformers.ts | 212 +++++++++++ resources/scripts/components/App.tsx | 15 +- .../scripts/components/admin/AdminBox.tsx | 36 ++ .../components/admin/AdminCheckbox.tsx | 36 ++ .../components/admin/AdminContentBlock.tsx | 42 +++ .../scripts/components/admin/AdminTable.tsx | 348 ++++++++++++++++++ .../scripts/components/admin/Sidebar.tsx | 87 +++++ .../components/admin/SubNavigation.tsx | 42 +++ .../admin/databases/DatabaseDeleteButton.tsx | 73 ++++ .../admin/databases/DatabaseEditContainer.tsx | 235 ++++++++++++ .../admin/databases/DatabasesContainer.tsx | 194 ++++++++++ .../admin/databases/NewDatabaseContainer.tsx | 48 +++ .../admin/locations/LocationDeleteButton.tsx | 74 ++++ .../admin/locations/LocationEditContainer.tsx | 180 +++++++++ .../admin/locations/LocationsContainer.tsx | 186 ++++++++++ .../admin/locations/NewLocationButton.tsx | 112 ++++++ .../admin/mounts/MountDeleteButton.tsx | 73 ++++ .../admin/mounts/MountEditContainer.tsx | 142 +++++++ .../components/admin/mounts/MountForm.tsx | 133 +++++++ .../admin/mounts/MountsContainer.tsx | 241 ++++++++++++ .../admin/mounts/NewMountContainer.tsx | 51 +++ .../admin/nests/ImportEggButton.tsx | 82 +++++ .../admin/nests/NestDeleteButton.tsx | 73 ++++ .../admin/nests/NestEditContainer.tsx | 250 +++++++++++++ .../components/admin/nests/NestEggTable.tsx | 160 ++++++++ .../components/admin/nests/NestsContainer.tsx | 182 +++++++++ .../admin/nests/NewEggContainer.tsx | 115 ++++++ .../components/admin/nests/NewNestButton.tsx | 112 ++++++ .../admin/nests/eggs/EggDeleteButton.tsx | 73 ++++ .../admin/nests/eggs/EggExportButton.tsx | 85 +++++ .../admin/nests/eggs/EggInstallContainer.tsx | 110 ++++++ .../components/admin/nests/eggs/EggRouter.tsx | 90 +++++ .../admin/nests/eggs/EggSettingsContainer.tsx | 245 ++++++++++++ .../nests/eggs/EggVariablesContainer.tsx | 218 +++++++++++ .../admin/nests/eggs/NewVariableButton.tsx | 103 ++++++ .../components/admin/nodes/DatabaseSelect.tsx | 56 +++ .../components/admin/nodes/LocationSelect.tsx | 56 +++ .../admin/nodes/NewNodeContainer.tsx | 127 +++++++ .../admin/nodes/NodeAboutContainer.tsx | 96 +++++ .../admin/nodes/NodeAllocationContainer.tsx | 27 ++ .../nodes/NodeConfigurationContainer.tsx | 70 ++++ .../admin/nodes/NodeDeleteButton.tsx | 73 ++++ .../admin/nodes/NodeEditContainer.tsx | 134 +++++++ .../admin/nodes/NodeLimitContainer.tsx | 47 +++ .../admin/nodes/NodeListenContainer.tsx | 37 ++ .../components/admin/nodes/NodeRouter.tsx | 146 ++++++++ .../components/admin/nodes/NodeServers.tsx | 10 + .../admin/nodes/NodeSettingsContainer.tsx | 95 +++++ .../components/admin/nodes/NodesContainer.tsx | 271 ++++++++++++++ .../nodes/allocations/AllocationTable.tsx | 216 +++++++++++ .../allocations/CreateAllocationForm.tsx | 118 ++++++ .../allocations/DeleteAllocationButton.tsx | 77 ++++ .../admin/overview/OverviewContainer.tsx | 103 ++++++ .../components/admin/roles/NewRoleButton.tsx | 107 ++++++ .../admin/roles/RoleDeleteButton.tsx | 73 ++++ .../admin/roles/RoleEditContainer.tsx | 176 +++++++++ .../components/admin/roles/RolesContainer.tsx | 182 +++++++++ .../components/admin/servers/EggSelect.tsx | 75 ++++ .../components/admin/servers/NestSelector.tsx | 44 +++ .../admin/servers/NewServerContainer.tsx | 225 +++++++++++ .../components/admin/servers/NodeSelect.tsx | 46 +++ .../components/admin/servers/OwnerSelect.tsx | 47 +++ .../admin/servers/ServerDeleteButton.tsx | 66 ++++ .../admin/servers/ServerManageContainer.tsx | 60 +++ .../components/admin/servers/ServerRouter.tsx | 76 ++++ .../admin/servers/ServerSettingsContainer.tsx | 103 ++++++ .../admin/servers/ServerStartupContainer.tsx | 258 +++++++++++++ .../admin/servers/ServersContainer.tsx | 36 ++ .../components/admin/servers/ServersTable.tsx | 236 ++++++++++++ .../servers/settings/BaseSettingsBox.tsx | 31 ++ .../servers/settings/FeatureLimitsBox.tsx | 38 ++ .../admin/servers/settings/NetworkingBox.tsx | 68 ++++ .../servers/settings/ServerResourceBox.tsx | 73 ++++ .../admin/settings/GeneralSettings.tsx | 37 ++ .../admin/settings/MailSettings.tsx | 102 +++++ .../admin/settings/SettingsContainer.tsx | 52 +++ .../admin/users/NewUserContainer.tsx | 49 +++ .../components/admin/users/RoleSelect.tsx | 56 +++ .../admin/users/UserAboutContainer.tsx | 62 ++++ .../admin/users/UserDeleteButton.tsx | 73 ++++ .../components/admin/users/UserForm.tsx | 148 ++++++++ .../components/admin/users/UserRouter.tsx | 114 ++++++ .../components/admin/users/UserServers.tsx | 10 + .../components/admin/users/UserTableRow.tsx | 79 ++++ .../components/admin/users/UsersContainer.tsx | 119 ++++++ .../activity/ActivityLogContainer.tsx | 2 +- .../dashboard/forms/RecoveryTokensDialog.tsx | 6 +- .../dashboard/forms/SetupTOTPDialog.tsx | 4 +- .../scripts/components/elements/Code.tsx | 2 +- .../components/elements/CopyOnClick.tsx | 2 +- .../scripts/components/elements/Editor.tsx | 318 ++++++++++++++++ .../scripts/components/elements/Field.tsx | 51 ++- .../components/elements/SearchableSelect.tsx | 315 ++++++++++++++++ .../components/elements/SelectField.tsx | 347 +++++++++++++++++ .../elements/activity/ActivityLogEntry.tsx | 12 +- .../activity/ActivityLogMetaButton.tsx | 4 +- .../elements/activity/style.module.css | 6 +- .../components/elements/alert/Alert.tsx | 2 +- .../elements/button/style.module.css | 14 +- .../components/elements/dialog/Dialog.tsx | 2 +- .../elements/dialog/DialogFooter.tsx | 4 +- .../elements/dialog/style.module.css | 6 +- .../elements/table/TFootPaginated.tsx | 23 ++ .../components/elements/tooltip/Tooltip.tsx | 4 +- resources/scripts/components/helpers.ts | 12 + .../server/ServerActivityLogContainer.tsx | 4 +- .../server/backups/BackupContextMenu.tsx | 6 +- .../components/server/console/ChartBlock.tsx | 2 +- .../components/server/console/Console.tsx | 2 +- .../server/console/ServerConsoleContainer.tsx | 2 +- .../server/console/ServerDetailsBlock.tsx | 10 +- .../components/server/console/StatBlock.tsx | 14 +- .../components/server/console/chart.ts | 4 +- .../server/console/style.module.css | 6 +- .../server/files/FileDropdownMenu.tsx | 2 +- .../server/files/FileManagerStatus.tsx | 4 +- .../server/files/MassActionsBar.tsx | 2 +- .../server/network/AllocationRow.tsx | 2 +- .../helpers/extractSearchFilters.spec.ts | 80 ++++ .../scripts/helpers/extractSearchFilters.ts | 49 +++ .../helpers/splitStringWhitespace.spec.ts | 16 + .../scripts/helpers/splitStringWhitespace.ts | 27 ++ .../scripts/plugins/useDebouncedState.ts | 12 + resources/scripts/routers/AdminRouter.tsx | 180 +++++++++ resources/scripts/state/admin/allocations.ts | 29 ++ resources/scripts/state/admin/databases.ts | 29 ++ resources/scripts/state/admin/index.ts | 44 +++ resources/scripts/state/admin/locations.ts | 29 ++ resources/scripts/state/admin/mounts.ts | 29 ++ resources/scripts/state/admin/nests.ts | 29 ++ resources/scripts/state/admin/nodes.ts | 29 ++ resources/scripts/state/admin/roles.ts | 29 ++ resources/scripts/state/admin/servers.ts | 29 ++ resources/scripts/state/admin/users.ts | 29 ++ routes/api-application.php | 100 ++--- tailwind.config.js | 17 +- yarn.lock | 221 ++++++++++- 199 files changed, 13387 insertions(+), 151 deletions(-) create mode 100644 app/Http/Middleware/Api/Application/SubstituteApplicationApiBindings.php create mode 100644 resources/scripts/api/admin/databases/createDatabase.ts create mode 100644 resources/scripts/api/admin/databases/deleteDatabase.ts create mode 100644 resources/scripts/api/admin/databases/getDatabase.ts create mode 100644 resources/scripts/api/admin/databases/getDatabases.ts create mode 100644 resources/scripts/api/admin/databases/searchDatabases.ts create mode 100644 resources/scripts/api/admin/databases/updateDatabase.ts create mode 100644 resources/scripts/api/admin/egg.ts create mode 100644 resources/scripts/api/admin/eggs/createEgg.ts create mode 100644 resources/scripts/api/admin/eggs/createEggVariable.ts create mode 100644 resources/scripts/api/admin/eggs/deleteEgg.ts create mode 100644 resources/scripts/api/admin/eggs/deleteEggVariable.ts create mode 100644 resources/scripts/api/admin/eggs/getEgg.ts create mode 100644 resources/scripts/api/admin/eggs/updateEgg.ts create mode 100644 resources/scripts/api/admin/eggs/updateEggVariables.ts create mode 100644 resources/scripts/api/admin/getVersion.ts create mode 100644 resources/scripts/api/admin/index.ts create mode 100644 resources/scripts/api/admin/location.ts create mode 100644 resources/scripts/api/admin/locations/createLocation.ts create mode 100644 resources/scripts/api/admin/locations/deleteLocation.ts create mode 100644 resources/scripts/api/admin/locations/getLocation.ts create mode 100644 resources/scripts/api/admin/locations/getLocations.ts create mode 100644 resources/scripts/api/admin/locations/searchLocations.ts create mode 100644 resources/scripts/api/admin/locations/updateLocation.ts create mode 100644 resources/scripts/api/admin/mounts/createMount.ts create mode 100644 resources/scripts/api/admin/mounts/deleteMount.ts create mode 100644 resources/scripts/api/admin/mounts/getMount.ts create mode 100644 resources/scripts/api/admin/mounts/getMounts.ts create mode 100644 resources/scripts/api/admin/mounts/updateMount.ts create mode 100644 resources/scripts/api/admin/nest.ts create mode 100644 resources/scripts/api/admin/nests/createNest.ts create mode 100644 resources/scripts/api/admin/nests/deleteNest.ts create mode 100644 resources/scripts/api/admin/nests/getEggs.ts create mode 100644 resources/scripts/api/admin/nests/getNest.ts create mode 100644 resources/scripts/api/admin/nests/getNests.ts create mode 100644 resources/scripts/api/admin/nests/importEgg.ts create mode 100644 resources/scripts/api/admin/nests/updateNest.ts create mode 100644 resources/scripts/api/admin/node.ts create mode 100644 resources/scripts/api/admin/nodes/allocations/createAllocation.ts create mode 100644 resources/scripts/api/admin/nodes/allocations/deleteAllocation.ts create mode 100644 resources/scripts/api/admin/nodes/allocations/getAllocations.ts create mode 100644 resources/scripts/api/admin/nodes/createNode.ts create mode 100644 resources/scripts/api/admin/nodes/deleteNode.ts create mode 100644 resources/scripts/api/admin/nodes/getAllocations.ts create mode 100644 resources/scripts/api/admin/nodes/getNode.ts create mode 100644 resources/scripts/api/admin/nodes/getNodeConfiguration.ts create mode 100644 resources/scripts/api/admin/nodes/getNodeInformation.ts create mode 100644 resources/scripts/api/admin/nodes/getNodes.ts create mode 100644 resources/scripts/api/admin/nodes/updateNode.ts create mode 100644 resources/scripts/api/admin/roles.ts create mode 100644 resources/scripts/api/admin/server.ts create mode 100644 resources/scripts/api/admin/servers/createServer.ts create mode 100644 resources/scripts/api/admin/servers/deleteServer.ts create mode 100644 resources/scripts/api/admin/servers/getServer.ts create mode 100644 resources/scripts/api/admin/servers/getServers.ts create mode 100644 resources/scripts/api/admin/servers/updateServer.ts create mode 100644 resources/scripts/api/admin/servers/updateServerStartup.ts create mode 100644 resources/scripts/api/admin/users.ts create mode 100644 resources/scripts/api/definitions/admin/index.ts create mode 100644 resources/scripts/api/definitions/admin/models.d.ts create mode 100644 resources/scripts/api/definitions/admin/transformers.ts create mode 100644 resources/scripts/components/admin/AdminBox.tsx create mode 100644 resources/scripts/components/admin/AdminCheckbox.tsx create mode 100644 resources/scripts/components/admin/AdminContentBlock.tsx create mode 100644 resources/scripts/components/admin/AdminTable.tsx create mode 100644 resources/scripts/components/admin/Sidebar.tsx create mode 100644 resources/scripts/components/admin/SubNavigation.tsx create mode 100644 resources/scripts/components/admin/databases/DatabaseDeleteButton.tsx create mode 100644 resources/scripts/components/admin/databases/DatabaseEditContainer.tsx create mode 100644 resources/scripts/components/admin/databases/DatabasesContainer.tsx create mode 100644 resources/scripts/components/admin/databases/NewDatabaseContainer.tsx create mode 100644 resources/scripts/components/admin/locations/LocationDeleteButton.tsx create mode 100644 resources/scripts/components/admin/locations/LocationEditContainer.tsx create mode 100644 resources/scripts/components/admin/locations/LocationsContainer.tsx create mode 100644 resources/scripts/components/admin/locations/NewLocationButton.tsx create mode 100644 resources/scripts/components/admin/mounts/MountDeleteButton.tsx create mode 100644 resources/scripts/components/admin/mounts/MountEditContainer.tsx create mode 100644 resources/scripts/components/admin/mounts/MountForm.tsx create mode 100644 resources/scripts/components/admin/mounts/MountsContainer.tsx create mode 100644 resources/scripts/components/admin/mounts/NewMountContainer.tsx create mode 100644 resources/scripts/components/admin/nests/ImportEggButton.tsx create mode 100644 resources/scripts/components/admin/nests/NestDeleteButton.tsx create mode 100644 resources/scripts/components/admin/nests/NestEditContainer.tsx create mode 100644 resources/scripts/components/admin/nests/NestEggTable.tsx create mode 100644 resources/scripts/components/admin/nests/NestsContainer.tsx create mode 100644 resources/scripts/components/admin/nests/NewEggContainer.tsx create mode 100644 resources/scripts/components/admin/nests/NewNestButton.tsx create mode 100644 resources/scripts/components/admin/nests/eggs/EggDeleteButton.tsx create mode 100644 resources/scripts/components/admin/nests/eggs/EggExportButton.tsx create mode 100644 resources/scripts/components/admin/nests/eggs/EggInstallContainer.tsx create mode 100644 resources/scripts/components/admin/nests/eggs/EggRouter.tsx create mode 100644 resources/scripts/components/admin/nests/eggs/EggSettingsContainer.tsx create mode 100644 resources/scripts/components/admin/nests/eggs/EggVariablesContainer.tsx create mode 100644 resources/scripts/components/admin/nests/eggs/NewVariableButton.tsx create mode 100644 resources/scripts/components/admin/nodes/DatabaseSelect.tsx create mode 100644 resources/scripts/components/admin/nodes/LocationSelect.tsx create mode 100644 resources/scripts/components/admin/nodes/NewNodeContainer.tsx create mode 100644 resources/scripts/components/admin/nodes/NodeAboutContainer.tsx create mode 100644 resources/scripts/components/admin/nodes/NodeAllocationContainer.tsx create mode 100644 resources/scripts/components/admin/nodes/NodeConfigurationContainer.tsx create mode 100644 resources/scripts/components/admin/nodes/NodeDeleteButton.tsx create mode 100644 resources/scripts/components/admin/nodes/NodeEditContainer.tsx create mode 100644 resources/scripts/components/admin/nodes/NodeLimitContainer.tsx create mode 100644 resources/scripts/components/admin/nodes/NodeListenContainer.tsx create mode 100644 resources/scripts/components/admin/nodes/NodeRouter.tsx create mode 100644 resources/scripts/components/admin/nodes/NodeServers.tsx create mode 100644 resources/scripts/components/admin/nodes/NodeSettingsContainer.tsx create mode 100644 resources/scripts/components/admin/nodes/NodesContainer.tsx create mode 100644 resources/scripts/components/admin/nodes/allocations/AllocationTable.tsx create mode 100644 resources/scripts/components/admin/nodes/allocations/CreateAllocationForm.tsx create mode 100644 resources/scripts/components/admin/nodes/allocations/DeleteAllocationButton.tsx create mode 100644 resources/scripts/components/admin/overview/OverviewContainer.tsx create mode 100644 resources/scripts/components/admin/roles/NewRoleButton.tsx create mode 100644 resources/scripts/components/admin/roles/RoleDeleteButton.tsx create mode 100644 resources/scripts/components/admin/roles/RoleEditContainer.tsx create mode 100644 resources/scripts/components/admin/roles/RolesContainer.tsx create mode 100644 resources/scripts/components/admin/servers/EggSelect.tsx create mode 100644 resources/scripts/components/admin/servers/NestSelector.tsx create mode 100644 resources/scripts/components/admin/servers/NewServerContainer.tsx create mode 100644 resources/scripts/components/admin/servers/NodeSelect.tsx create mode 100644 resources/scripts/components/admin/servers/OwnerSelect.tsx create mode 100644 resources/scripts/components/admin/servers/ServerDeleteButton.tsx create mode 100644 resources/scripts/components/admin/servers/ServerManageContainer.tsx create mode 100644 resources/scripts/components/admin/servers/ServerRouter.tsx create mode 100644 resources/scripts/components/admin/servers/ServerSettingsContainer.tsx create mode 100644 resources/scripts/components/admin/servers/ServerStartupContainer.tsx create mode 100644 resources/scripts/components/admin/servers/ServersContainer.tsx create mode 100644 resources/scripts/components/admin/servers/ServersTable.tsx create mode 100644 resources/scripts/components/admin/servers/settings/BaseSettingsBox.tsx create mode 100644 resources/scripts/components/admin/servers/settings/FeatureLimitsBox.tsx create mode 100644 resources/scripts/components/admin/servers/settings/NetworkingBox.tsx create mode 100644 resources/scripts/components/admin/servers/settings/ServerResourceBox.tsx create mode 100644 resources/scripts/components/admin/settings/GeneralSettings.tsx create mode 100644 resources/scripts/components/admin/settings/MailSettings.tsx create mode 100644 resources/scripts/components/admin/settings/SettingsContainer.tsx create mode 100644 resources/scripts/components/admin/users/NewUserContainer.tsx create mode 100644 resources/scripts/components/admin/users/RoleSelect.tsx create mode 100644 resources/scripts/components/admin/users/UserAboutContainer.tsx create mode 100644 resources/scripts/components/admin/users/UserDeleteButton.tsx create mode 100644 resources/scripts/components/admin/users/UserForm.tsx create mode 100644 resources/scripts/components/admin/users/UserRouter.tsx create mode 100644 resources/scripts/components/admin/users/UserServers.tsx create mode 100644 resources/scripts/components/admin/users/UserTableRow.tsx create mode 100644 resources/scripts/components/admin/users/UsersContainer.tsx create mode 100644 resources/scripts/components/elements/Editor.tsx create mode 100644 resources/scripts/components/elements/SearchableSelect.tsx create mode 100644 resources/scripts/components/elements/SelectField.tsx create mode 100644 resources/scripts/components/elements/table/TFootPaginated.tsx create mode 100644 resources/scripts/components/helpers.ts create mode 100644 resources/scripts/helpers/extractSearchFilters.spec.ts create mode 100644 resources/scripts/helpers/extractSearchFilters.ts create mode 100644 resources/scripts/helpers/splitStringWhitespace.spec.ts create mode 100644 resources/scripts/helpers/splitStringWhitespace.ts create mode 100644 resources/scripts/plugins/useDebouncedState.ts create mode 100644 resources/scripts/routers/AdminRouter.tsx create mode 100644 resources/scripts/state/admin/allocations.ts create mode 100644 resources/scripts/state/admin/databases.ts create mode 100644 resources/scripts/state/admin/index.ts create mode 100644 resources/scripts/state/admin/locations.ts create mode 100644 resources/scripts/state/admin/mounts.ts create mode 100644 resources/scripts/state/admin/nests.ts create mode 100644 resources/scripts/state/admin/nodes.ts create mode 100644 resources/scripts/state/admin/roles.ts create mode 100644 resources/scripts/state/admin/servers.ts create mode 100644 resources/scripts/state/admin/users.ts diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 7df1ed2b10..ad03187208 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -33,6 +33,7 @@ use Pterodactyl\Http\Middleware\Api\Client\SubstituteClientBindings; use Illuminate\Foundation\Http\Middleware\PreventRequestsDuringMaintenance; use Pterodactyl\Http\Middleware\Api\Application\AuthenticateApplicationUser; +use Pterodactyl\Http\Middleware\Api\Application\SubstituteApplicationApiBindings; class Kernel extends HttpKernel { @@ -70,6 +71,7 @@ class Kernel extends HttpKernel AuthenticateIPAccess::class, ], 'application-api' => [ +// SubstituteApplicationApiBindings::class, SubstituteBindings::class, AuthenticateApplicationUser::class, ], diff --git a/app/Http/Middleware/Api/Application/SubstituteApplicationApiBindings.php b/app/Http/Middleware/Api/Application/SubstituteApplicationApiBindings.php new file mode 100644 index 0000000000..e629f6ca6c --- /dev/null +++ b/app/Http/Middleware/Api/Application/SubstituteApplicationApiBindings.php @@ -0,0 +1,66 @@ + Allocation::class, + 'database' => Database::class, + 'egg' => Egg::class, + 'location' => Location::class, + 'nest' => Nest::class, + 'node' => Node::class, + 'server' => Server::class, + 'user' => User::class, + ]; + + public function __construct(Registrar $router) + { + $this->router = $router; + } + + /** + * Perform substitution of route parameters without triggering + * a 404 error if a model is not found. + * + * @param \Illuminate\Http\Request $request + * + * @return mixed + */ + public function handle($request, Closure $next) + { + foreach (self::$mappings as $key => $class) { + $this->router->bind($key, $class); + } + + try { + $this->router->substituteImplicitBindings($route = $request->route()); + } catch (ModelNotFoundException $exception) { + if (!empty($route) && $route->getMissing()) { + $route->getMissing()($request); + } + + throw $exception; + } + + return $next($request); + } +} diff --git a/package.json b/package.json index 6641cd841e..00b8764a14 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "@codemirror/view": "^6.0.0", "@floating-ui/react-dom-interactions": "0.13.3", "@fortawesome/fontawesome-svg-core": "6.2.1", + "@fortawesome/free-brands-svg-icons": "6.2.1", "@fortawesome/free-solid-svg-icons": "6.2.1", "@fortawesome/react-fontawesome": "0.2.0", "@flyyer/use-fit-text": "3.0.1", @@ -72,6 +73,7 @@ "react-fast-compare": "3.2.0", "react-i18next": "12.1.1", "react-router-dom": "6.4.5", + "react-select": "5.7.0", "reaptcha": "1.12.1", "sockette": "2.0.6", "styled-components": "5.3.6", @@ -109,7 +111,7 @@ "eslint-plugin-react": "7.31.11", "eslint-plugin-react-hooks": "4.6.0", "happy-dom": "8.1.0", - "laravel-vite-plugin": "0.7.1", + "laravel-vite-plugin": "0.7.2", "pathe": "1.0.0", "postcss": "8.4.20", "postcss-nesting": "10.2.0", diff --git a/resources/scripts/api/admin/databases/createDatabase.ts b/resources/scripts/api/admin/databases/createDatabase.ts new file mode 100644 index 0000000000..98d37bf1dc --- /dev/null +++ b/resources/scripts/api/admin/databases/createDatabase.ts @@ -0,0 +1,12 @@ +import http from '@/api/http'; +import { Database, rawDataToDatabase } from '@/api/admin/databases/getDatabases'; + +export default (name: string, host: string, port: number, username: string, password: string, include: string[] = []): Promise => { + return new Promise((resolve, reject) => { + http.post('/api/application/databases', { + name, host, port, username, password, + }, { params: { include: include.join(',') } }) + .then(({ data }) => resolve(rawDataToDatabase(data))) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/admin/databases/deleteDatabase.ts b/resources/scripts/api/admin/databases/deleteDatabase.ts new file mode 100644 index 0000000000..436aeaa852 --- /dev/null +++ b/resources/scripts/api/admin/databases/deleteDatabase.ts @@ -0,0 +1,9 @@ +import http from '@/api/http'; + +export default (id: number): Promise => { + return new Promise((resolve, reject) => { + http.delete(`/api/application/databases/${id}`) + .then(() => resolve()) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/admin/databases/getDatabase.ts b/resources/scripts/api/admin/databases/getDatabase.ts new file mode 100644 index 0000000000..0af69fcd65 --- /dev/null +++ b/resources/scripts/api/admin/databases/getDatabase.ts @@ -0,0 +1,10 @@ +import http from '@/api/http'; +import { Database, rawDataToDatabase } from '@/api/admin/databases/getDatabases'; + +export default (id: number, include: string[] = []): Promise => { + return new Promise((resolve, reject) => { + http.get(`/api/application/databases/${id}`, { params: { include: include.join(',') } }) + .then(({ data }) => resolve(rawDataToDatabase(data))) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/admin/databases/getDatabases.ts b/resources/scripts/api/admin/databases/getDatabases.ts new file mode 100644 index 0000000000..4ad5f4a3d8 --- /dev/null +++ b/resources/scripts/api/admin/databases/getDatabases.ts @@ -0,0 +1,64 @@ +import http, { FractalResponseData, getPaginationSet, PaginatedResult } from '@/api/http'; +import { useContext } from 'react'; +import useSWR from 'swr'; +import { createContext } from '@/api/admin'; + +export interface Database { + id: number; + name: string; + host: string; + port: number; + username: string; + maxDatabases: number; + createdAt: Date; + updatedAt: Date; + + getAddress (): string; +} + +export const rawDataToDatabase = ({ attributes }: FractalResponseData): Database => ({ + id: attributes.id, + name: attributes.name, + host: attributes.host, + port: attributes.port, + username: attributes.username, + maxDatabases: attributes.max_databases, + createdAt: new Date(attributes.created_at), + updatedAt: new Date(attributes.updated_at), + + getAddress: () => `${attributes.host}:${attributes.port}`, +}); + +export interface Filters { + id?: string; + name?: string; + host?: string; +} + +export const Context = createContext(); + +export default (include: string[] = []) => { + const { page, filters, sort, sortDirection } = useContext(Context); + + const params = {}; + if (filters !== null) { + Object.keys(filters).forEach(key => { + // @ts-ignore + params['filter[' + key + ']'] = filters[key]; + }); + } + + if (sort !== null) { + // @ts-ignore + params.sort = (sortDirection ? '-' : '') + sort; + } + + return useSWR>([ 'databases', page, filters, sort, sortDirection ], async () => { + const { data } = await http.get('/api/application/databases', { params: { include: include.join(','), page, ...params } }); + + return ({ + items: (data.data || []).map(rawDataToDatabase), + pagination: getPaginationSet(data.meta.pagination), + }); + }); +}; diff --git a/resources/scripts/api/admin/databases/searchDatabases.ts b/resources/scripts/api/admin/databases/searchDatabases.ts new file mode 100644 index 0000000000..99533b5dc6 --- /dev/null +++ b/resources/scripts/api/admin/databases/searchDatabases.ts @@ -0,0 +1,25 @@ +import http from '@/api/http'; +import { Database, rawDataToDatabase } from '@/api/admin/databases/getDatabases'; + +interface Filters { + name?: string; + host?: string; +} + +export default (filters?: Filters): Promise => { + const params = {}; + if (filters !== undefined) { + Object.keys(filters).forEach(key => { + // @ts-ignore + params['filter[' + key + ']'] = filters[key]; + }); + } + + return new Promise((resolve, reject) => { + http.get('/api/application/databases', { params }) + .then(response => resolve( + (response.data.data || []).map(rawDataToDatabase) + )) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/admin/databases/updateDatabase.ts b/resources/scripts/api/admin/databases/updateDatabase.ts new file mode 100644 index 0000000000..7d01a9024b --- /dev/null +++ b/resources/scripts/api/admin/databases/updateDatabase.ts @@ -0,0 +1,12 @@ +import http from '@/api/http'; +import { Database, rawDataToDatabase } from '@/api/admin/databases/getDatabases'; + +export default (id: number, name: string, host: string, port: number, username: string, password: string | undefined, include: string[] = []): Promise => { + return new Promise((resolve, reject) => { + http.patch(`/api/application/databases/${id}`, { + name, host, port, username, password, + }, { params: { include: include.join(',') } }) + .then(({ data }) => resolve(rawDataToDatabase(data))) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/admin/egg.ts b/resources/scripts/api/admin/egg.ts new file mode 100644 index 0000000000..874fd7ad75 --- /dev/null +++ b/resources/scripts/api/admin/egg.ts @@ -0,0 +1,104 @@ +import type { AxiosError } from 'axios'; +import { useParams } from 'react-router-dom'; +import type { SWRResponse } from 'swr'; +import useSWR from 'swr'; + +import type { Model, UUID, WithRelationships } from '@/api/admin/index'; +import { withRelationships } from '@/api/admin/index'; +import type { Nest } from '@/api/admin/nest'; +import type { QueryBuilderParams } from '@/api/http'; +import http, { withQueryBuilderParams } from '@/api/http'; +import { Transformers } from '@definitions/admin'; + +export interface Egg extends Model { + id: number; + uuid: UUID; + nestId: number; + author: string; + name: string; + description: string | null; + features: string[] | null; + dockerImages: Record; + configFiles: Record | null; + configStartup: Record | null; + configStop: string | null; + configFrom: number | null; + startup: string; + scriptContainer: string; + copyScriptFrom: number | null; + scriptEntry: string; + scriptIsPrivileged: boolean; + scriptInstall: string | null; + createdAt: Date; + updatedAt: Date; + relationships: { + nest?: Nest; + variables?: EggVariable[]; + }; +} + +export interface EggVariable extends Model { + id: number; + eggId: number; + name: string; + description: string; + environmentVariable: string; + defaultValue: string; + isUserViewable: boolean; + isUserEditable: boolean; + // isRequired: boolean; + rules: string; + createdAt: Date; + updatedAt: Date; +} + +/** + * A standard API response with the minimum viable details for the frontend + * to correctly render a egg. + */ +type LoadedEgg = WithRelationships; + +/** + * Gets a single egg from the database and returns it. + */ +export const getEgg = async (id: number | string): Promise => { + const { data } = await http.get(`/api/application/eggs/${id}`, { + params: { + include: ['nest', 'variables'], + }, + }); + + return withRelationships(Transformers.toEgg(data), 'nest', 'variables'); +}; + +export const searchEggs = async ( + nestId: number, + params: QueryBuilderParams<'name'>, +): Promise[]> => { + const { data } = await http.get(`/api/application/nests/${nestId}/eggs`, { + params: { + ...withQueryBuilderParams(params), + include: ['variables'], + }, + }); + + return data.data.map(Transformers.toEgg); +}; + +export const exportEgg = async (eggId: number): Promise> => { + const { data } = await http.get(`/api/application/eggs/${eggId}/export`); + return data; +}; + +/** + * Returns an SWR instance by automatically loading in the server for the currently + * loaded route match in the admin area. + */ +export const useEggFromRoute = (): SWRResponse => { + const params = useParams<'id'>(); + + return useSWR(`/api/application/eggs/${params.id}`, async () => getEgg(Number(params.id)), { + revalidateOnMount: false, + revalidateOnFocus: false, + }); +}; diff --git a/resources/scripts/api/admin/eggs/createEgg.ts b/resources/scripts/api/admin/eggs/createEgg.ts new file mode 100644 index 0000000000..0ad08d9eed --- /dev/null +++ b/resources/scripts/api/admin/eggs/createEgg.ts @@ -0,0 +1,31 @@ +import http from '@/api/http'; +import { Egg, rawDataToEgg } from '@/api/admin/eggs/getEgg'; + +type Egg2 = Omit, 'configFiles'>, 'configStartup'> & { configFiles: string, configStartup: string }; + +export default (egg: Partial): Promise => { + return new Promise((resolve, reject) => { + http.post( + '/api/application/eggs', + { + nest_id: egg.nestId, + name: egg.name, + description: egg.description, + features: egg.features, + docker_images: egg.dockerImages, + config_files: egg.configFiles, + config_startup: egg.configStartup, + config_stop: egg.configStop, + config_from: egg.configFrom, + startup: egg.startup, + script_container: egg.scriptContainer, + copy_script_from: egg.copyScriptFrom, + script_entry: egg.scriptEntry, + script_is_privileged: egg.scriptIsPrivileged, + script_install: egg.scriptInstall, + }, + ) + .then(({ data }) => resolve(rawDataToEgg(data))) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/admin/eggs/createEggVariable.ts b/resources/scripts/api/admin/eggs/createEggVariable.ts new file mode 100644 index 0000000000..375283d2c4 --- /dev/null +++ b/resources/scripts/api/admin/eggs/createEggVariable.ts @@ -0,0 +1,22 @@ +import http from '@/api/http'; +import { EggVariable } from '@/api/admin/egg'; +import { Transformers } from '@definitions/admin'; + +export type CreateEggVariable = Omit; + +export default async (eggId: number, variable: CreateEggVariable): Promise => { + const { data } = await http.post( + `/api/application/eggs/${eggId}/variables`, + { + name: variable.name, + description: variable.description, + env_variable: variable.environmentVariable, + default_value: variable.defaultValue, + user_viewable: variable.isUserViewable, + user_editable: variable.isUserEditable, + rules: variable.rules, + }, + ); + + return Transformers.toEggVariable(data); +}; diff --git a/resources/scripts/api/admin/eggs/deleteEgg.ts b/resources/scripts/api/admin/eggs/deleteEgg.ts new file mode 100644 index 0000000000..635f3d6c2a --- /dev/null +++ b/resources/scripts/api/admin/eggs/deleteEgg.ts @@ -0,0 +1,9 @@ +import http from '@/api/http'; + +export default (id: number): Promise => { + return new Promise((resolve, reject) => { + http.delete(`/api/application/eggs/${id}`) + .then(() => resolve()) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/admin/eggs/deleteEggVariable.ts b/resources/scripts/api/admin/eggs/deleteEggVariable.ts new file mode 100644 index 0000000000..967798f554 --- /dev/null +++ b/resources/scripts/api/admin/eggs/deleteEggVariable.ts @@ -0,0 +1,9 @@ +import http from '@/api/http'; + +export default (eggId: number, variableId: number): Promise => { + return new Promise((resolve, reject) => { + http.delete(`/api/application/eggs/${eggId}/variables/${variableId}`) + .then(() => resolve()) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/admin/eggs/getEgg.ts b/resources/scripts/api/admin/eggs/getEgg.ts new file mode 100644 index 0000000000..2a6bb0633e --- /dev/null +++ b/resources/scripts/api/admin/eggs/getEgg.ts @@ -0,0 +1,108 @@ +import { Nest } from '@/api/admin/nests/getNests'; +import { rawDataToServer, Server } from '@/api/admin/servers/getServers'; +import http, { FractalResponseData, FractalResponseList } from '@/api/http'; +import useSWR from 'swr'; + +export interface EggVariable { + id: number; + eggId: number; + name: string; + description: string; + envVariable: string; + defaultValue: string; + userViewable: boolean; + userEditable: boolean; + rules: string; + createdAt: Date; + updatedAt: Date; +} + +export const rawDataToEggVariable = ({ attributes }: FractalResponseData): EggVariable => ({ + id: attributes.id, + eggId: attributes.egg_id, + name: attributes.name, + description: attributes.description, + envVariable: attributes.env_variable, + defaultValue: attributes.default_value, + userViewable: attributes.user_viewable, + userEditable: attributes.user_editable, + rules: attributes.rules, + createdAt: new Date(attributes.created_at), + updatedAt: new Date(attributes.updated_at), +}); + +export interface Egg { + id: number; + uuid: string; + nestId: number; + author: string; + name: string; + description: string | null; + features: string[] | null; + dockerImages: Record; + configFiles: Record | null; + configStartup: Record | null; + configStop: string | null; + configFrom: number | null; + startup: string; + scriptContainer: string; + copyScriptFrom: number | null; + scriptEntry: string; + scriptIsPrivileged: boolean; + scriptInstall: string | null; + createdAt: Date; + updatedAt: Date; + + relations: { + nest?: Nest; + servers?: Server[]; + variables?: EggVariable[]; + }; +} + +export const rawDataToEgg = ({ attributes }: FractalResponseData): Egg => ({ + id: attributes.id, + uuid: attributes.uuid, + nestId: attributes.nest_id, + author: attributes.author, + name: attributes.name, + description: attributes.description, + features: attributes.features, + dockerImages: attributes.docker_images, + configFiles: attributes.config?.files, + configStartup: attributes.config?.startup, + configStop: attributes.config?.stop, + configFrom: attributes.config?.extends, + startup: attributes.startup, + copyScriptFrom: attributes.copy_script_from, + scriptContainer: attributes.script?.container, + scriptEntry: attributes.script?.entry, + scriptIsPrivileged: attributes.script?.privileged, + scriptInstall: attributes.script?.install, + createdAt: new Date(attributes.created_at), + updatedAt: new Date(attributes.updated_at), + + relations: { + nest: undefined, + servers: ((attributes.relationships?.servers as FractalResponseList | undefined)?.data || []).map( + rawDataToServer, + ), + variables: ((attributes.relationships?.variables as FractalResponseList | undefined)?.data || []).map( + rawDataToEggVariable, + ), + }, +}); + +export const getEgg = async (id: number): Promise => { + const { data } = await http.get(`/api/application/eggs/${id}`, { params: { include: ['variables'] } }); + + return rawDataToEgg(data); +}; + +export default (id: number) => { + return useSWR(`egg:${id}`, async () => { + const { data } = await http.get(`/api/application/eggs/${id}`, { params: { include: ['variables'] } }); + + return rawDataToEgg(data); + }); +}; diff --git a/resources/scripts/api/admin/eggs/updateEgg.ts b/resources/scripts/api/admin/eggs/updateEgg.ts new file mode 100644 index 0000000000..2500ba6b22 --- /dev/null +++ b/resources/scripts/api/admin/eggs/updateEgg.ts @@ -0,0 +1,31 @@ +import http from '@/api/http'; +import { Egg, rawDataToEgg } from '@/api/admin/eggs/getEgg'; + +type Egg2 = Omit, 'configFiles'>, 'configStartup'> & { configFiles?: string, configStartup?: string }; + +export default (id: number, egg: Partial): Promise => { + return new Promise((resolve, reject) => { + http.patch( + `/api/application/eggs/${id}`, + { + nest_id: egg.nestId, + name: egg.name, + description: egg.description, + features: egg.features, + docker_images: egg.dockerImages, + config_files: egg.configFiles, + config_startup: egg.configStartup, + config_stop: egg.configStop, + config_from: egg.configFrom, + startup: egg.startup, + script_container: egg.scriptContainer, + copy_script_from: egg.copyScriptFrom, + script_entry: egg.scriptEntry, + script_is_privileged: egg.scriptIsPrivileged, + script_install: egg.scriptInstall, + }, + ) + .then(({ data }) => resolve(rawDataToEgg(data))) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/admin/eggs/updateEggVariables.ts b/resources/scripts/api/admin/eggs/updateEggVariables.ts new file mode 100644 index 0000000000..b5d97b9522 --- /dev/null +++ b/resources/scripts/api/admin/eggs/updateEggVariables.ts @@ -0,0 +1,21 @@ +import http from '@/api/http'; +import { EggVariable } from '@/api/admin/egg'; +import { Transformers } from '@definitions/admin'; + +export default async (eggId: number, variables: Omit[]): Promise => { + const { data } = await http.patch( + `/api/application/eggs/${eggId}/variables`, + variables.map(variable => ({ + id: variable.id, + name: variable.name, + description: variable.description, + env_variable: variable.environmentVariable, + default_value: variable.defaultValue, + user_viewable: variable.isUserViewable, + user_editable: variable.isUserEditable, + rules: variable.rules, + })), + ); + + return data.data.map(Transformers.toEggVariable); +}; diff --git a/resources/scripts/api/admin/getVersion.ts b/resources/scripts/api/admin/getVersion.ts new file mode 100644 index 0000000000..3f24911e48 --- /dev/null +++ b/resources/scripts/api/admin/getVersion.ts @@ -0,0 +1,22 @@ +import http from '@/api/http'; + +export interface VersionData { + panel: { + current: string; + latest: string; + } + + wings: { + latest: string; + } + + git: string | null; +} + +export default (): Promise => { + return new Promise((resolve, reject) => { + http.get('/api/application/version') + .then(({ data }) => resolve(data)) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/admin/index.ts b/resources/scripts/api/admin/index.ts new file mode 100644 index 0000000000..014a207a72 --- /dev/null +++ b/resources/scripts/api/admin/index.ts @@ -0,0 +1,66 @@ +import { createContext } from 'react'; + +export interface Model { + relationships: Record; +} + +export type UUID = string; + +/** + * Marks the provided relationships keys as present in the given model + * rather than being optional to improve typing responses. + */ +export type WithRelationships = Omit & { + relationships: Omit & { + [K in R]: NonNullable; + } +} + +/** + * Helper type that allows you to infer the type of an object by giving + * it the specific API request function with a return type. For example: + * + * type EggT = InferModel; + */ +export type InferModel any> = ReturnType extends Promise ? U : T; + +/** + * Helper function that just returns the model you pass in, but types the model + * such that TypeScript understands the relationships on it. This is just to help + * reduce the amount of duplicated type casting all over the codebase. + */ +export const withRelationships = (model: M, ..._keys: R[]) => { + return model as unknown as WithRelationships; +}; + +export interface ListContext { + page: number; + setPage: (page: ((p: number) => number) | number) => void; + + filters: T | null; + setFilters: (filters: ((f: T | null) => T | null) | T | null) => void; + + sort: string | null; + setSort: (sort: string | null) => void; + + sortDirection: boolean; + setSortDirection: (direction: ((p: boolean) => boolean) | boolean) => void; +} + +function create () { + return createContext>({ + page: 1, + setPage: () => 1, + + filters: null, + setFilters: () => null, + + sort: null, + setSort: () => null, + + sortDirection: false, + setSortDirection: () => false, + }); +} + +export { create as createContext }; diff --git a/resources/scripts/api/admin/location.ts b/resources/scripts/api/admin/location.ts new file mode 100644 index 0000000000..82ff394f8b --- /dev/null +++ b/resources/scripts/api/admin/location.ts @@ -0,0 +1,13 @@ +import { Model } from '@/api/admin/index'; +import { Node } from '@/api/admin/node'; + +export interface Location extends Model { + id: number; + short: string; + long: string; + createdAt: Date; + updatedAt: Date; + relationships: { + nodes?: Node[]; + }; +} diff --git a/resources/scripts/api/admin/locations/createLocation.ts b/resources/scripts/api/admin/locations/createLocation.ts new file mode 100644 index 0000000000..053148fc43 --- /dev/null +++ b/resources/scripts/api/admin/locations/createLocation.ts @@ -0,0 +1,12 @@ +import http from '@/api/http'; +import { Location, rawDataToLocation } from '@/api/admin/locations/getLocations'; + +export default (short: string, long: string | null, include: string[] = []): Promise => { + return new Promise((resolve, reject) => { + http.post('/api/application/locations', { + short, long, + }, { params: { include: include.join(',') } }) + .then(({ data }) => resolve(rawDataToLocation(data))) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/admin/locations/deleteLocation.ts b/resources/scripts/api/admin/locations/deleteLocation.ts new file mode 100644 index 0000000000..85b42d60e4 --- /dev/null +++ b/resources/scripts/api/admin/locations/deleteLocation.ts @@ -0,0 +1,9 @@ +import http from '@/api/http'; + +export default (id: number): Promise => { + return new Promise((resolve, reject) => { + http.delete(`/api/application/locations/${id}`) + .then(() => resolve()) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/admin/locations/getLocation.ts b/resources/scripts/api/admin/locations/getLocation.ts new file mode 100644 index 0000000000..9a1aad4bce --- /dev/null +++ b/resources/scripts/api/admin/locations/getLocation.ts @@ -0,0 +1,10 @@ +import http from '@/api/http'; +import { Location, rawDataToLocation } from '@/api/admin/locations/getLocations'; + +export default (id: number, include: string[] = []): Promise => { + return new Promise((resolve, reject) => { + http.get(`/api/application/locations/${id}`, { params: { include: include.join(',') } }) + .then(({ data }) => resolve(rawDataToLocation(data))) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/admin/locations/getLocations.ts b/resources/scripts/api/admin/locations/getLocations.ts new file mode 100644 index 0000000000..f43567e513 --- /dev/null +++ b/resources/scripts/api/admin/locations/getLocations.ts @@ -0,0 +1,54 @@ +import http, { FractalResponseData, getPaginationSet, PaginatedResult } from '@/api/http'; +import { useContext } from 'react'; +import useSWR from 'swr'; +import { createContext } from '@/api/admin'; + +export interface Location { + id: number; + short: string; + long: string; + createdAt: Date; + updatedAt: Date; +} + +export const rawDataToLocation = ({ attributes }: FractalResponseData): Location => ({ + id: attributes.id, + short: attributes.short, + long: attributes.long, + createdAt: new Date(attributes.created_at), + updatedAt: new Date(attributes.updated_at), +}); + +export interface Filters { + id?: string; + short?: string; + long?: string; +} + +export const Context = createContext(); + +export default (include: string[] = []) => { + const { page, filters, sort, sortDirection } = useContext(Context); + + const params = {}; + if (filters !== null) { + Object.keys(filters).forEach(key => { + // @ts-ignore + params['filter[' + key + ']'] = filters[key]; + }); + } + + if (sort !== null) { + // @ts-ignore + params.sort = (sortDirection ? '-' : '') + sort; + } + + return useSWR>([ 'locations', page, filters, sort, sortDirection ], async () => { + const { data } = await http.get('/api/application/locations', { params: { include: include.join(','), page, ...params } }); + + return ({ + items: (data.data || []).map(rawDataToLocation), + pagination: getPaginationSet(data.meta.pagination), + }); + }); +}; diff --git a/resources/scripts/api/admin/locations/searchLocations.ts b/resources/scripts/api/admin/locations/searchLocations.ts new file mode 100644 index 0000000000..6edc50e625 --- /dev/null +++ b/resources/scripts/api/admin/locations/searchLocations.ts @@ -0,0 +1,25 @@ +import http from '@/api/http'; +import { Location, rawDataToLocation } from '@/api/admin/locations/getLocations'; + +interface Filters { + short?: string; + long?: string; +} + +export default (filters?: Filters): Promise => { + const params = {}; + if (filters !== undefined) { + Object.keys(filters).forEach(key => { + // @ts-ignore + params['filter[' + key + ']'] = filters[key]; + }); + } + + return new Promise((resolve, reject) => { + http.get('/api/application/locations', { params }) + .then(response => resolve( + (response.data.data || []).map(rawDataToLocation) + )) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/admin/locations/updateLocation.ts b/resources/scripts/api/admin/locations/updateLocation.ts new file mode 100644 index 0000000000..bbb5f8f4c1 --- /dev/null +++ b/resources/scripts/api/admin/locations/updateLocation.ts @@ -0,0 +1,12 @@ +import http from '@/api/http'; +import { Location, rawDataToLocation } from '@/api/admin/locations/getLocations'; + +export default (id: number, short: string, long: string | null, include: string[] = []): Promise => { + return new Promise((resolve, reject) => { + http.patch(`/api/application/locations/${id}`, { + short, long, + }, { params: { include: include.join(',') } }) + .then(({ data }) => resolve(rawDataToLocation(data))) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/admin/mounts/createMount.ts b/resources/scripts/api/admin/mounts/createMount.ts new file mode 100644 index 0000000000..63538c821b --- /dev/null +++ b/resources/scripts/api/admin/mounts/createMount.ts @@ -0,0 +1,12 @@ +import http from '@/api/http'; +import { Mount, rawDataToMount } from '@/api/admin/mounts/getMounts'; + +export default (name: string, description: string, source: string, target: string, readOnly: boolean, userMountable: boolean, include: string[] = []): Promise => { + return new Promise((resolve, reject) => { + http.post('/api/application/mounts', { + name, description, source, target, read_only: readOnly, user_mountable: userMountable, + }, { params: { include: include.join(',') } }) + .then(({ data }) => resolve(rawDataToMount(data))) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/admin/mounts/deleteMount.ts b/resources/scripts/api/admin/mounts/deleteMount.ts new file mode 100644 index 0000000000..e4ec1d1137 --- /dev/null +++ b/resources/scripts/api/admin/mounts/deleteMount.ts @@ -0,0 +1,9 @@ +import http from '@/api/http'; + +export default (id: number): Promise => { + return new Promise((resolve, reject) => { + http.delete(`/api/application/mounts/${id}`) + .then(() => resolve()) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/admin/mounts/getMount.ts b/resources/scripts/api/admin/mounts/getMount.ts new file mode 100644 index 0000000000..9bd8a56e98 --- /dev/null +++ b/resources/scripts/api/admin/mounts/getMount.ts @@ -0,0 +1,10 @@ +import http from '@/api/http'; +import { Mount, rawDataToMount } from '@/api/admin/mounts/getMounts'; + +export default (id: number, include: string[] = []): Promise => { + return new Promise((resolve, reject) => { + http.get(`/api/application/mounts/${id}`, { params: { include: include.join(',') } }) + .then(({ data }) => resolve(rawDataToMount(data))) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/admin/mounts/getMounts.ts b/resources/scripts/api/admin/mounts/getMounts.ts new file mode 100644 index 0000000000..449308119b --- /dev/null +++ b/resources/scripts/api/admin/mounts/getMounts.ts @@ -0,0 +1,80 @@ +import http, { FractalResponseData, FractalResponseList, getPaginationSet, PaginatedResult } from '@/api/http'; +import { useContext } from 'react'; +import useSWR from 'swr'; +import { createContext } from '@/api/admin'; +import { Egg, rawDataToEgg } from '@/api/admin/eggs/getEgg'; +import { Node, rawDataToNode } from '@/api/admin/nodes/getNodes'; +import { Server, rawDataToServer } from '@/api/admin/servers/getServers'; + +export interface Mount { + id: number; + uuid: string; + name: string; + description?: string; + source: string; + target: string; + readOnly: boolean; + userMountable: boolean; + createdAt: Date; + updatedAt: Date; + + relations: { + eggs: Egg[] | undefined; + nodes: Node[] | undefined; + servers: Server[] | undefined; + }; +} + +export const rawDataToMount = ({ attributes }: FractalResponseData): Mount => ({ + id: attributes.id, + uuid: attributes.uuid, + name: attributes.name, + description: attributes.description, + source: attributes.source, + target: attributes.target, + readOnly: attributes.read_only, + userMountable: attributes.user_mountable, + createdAt: new Date(attributes.created_at), + updatedAt: new Date(attributes.updated_at), + + relations: { + eggs: ((attributes.relationships?.eggs as FractalResponseList | undefined)?.data || []).map(rawDataToEgg), + nodes: ((attributes.relationships?.nodes as FractalResponseList | undefined)?.data || []).map(rawDataToNode), + servers: ((attributes.relationships?.servers as FractalResponseList | undefined)?.data || []).map(rawDataToServer), + }, +}); + +export interface Filters { + id?: string; + name?: string; + source?: string; + target?: string; +} + +export const Context = createContext(); + +export default (include: string[] = []) => { + const { page, filters, sort, sortDirection } = useContext(Context); + + const params = {}; + if (filters !== null) { + Object.keys(filters).forEach(key => { + // @ts-ignore + params['filter[' + key + ']'] = filters[key]; + }); + } + + if (sort !== null) { + // @ts-ignore + params.sort = (sortDirection ? '-' : '') + sort; + } + + return useSWR>([ 'mounts', page, filters, sort, sortDirection ], async () => { + const { data } = await http.get('/api/application/mounts', { params: { include: include.join(','), page, ...params } }); + + return ({ + items: (data.data || []).map(rawDataToMount), + pagination: getPaginationSet(data.meta.pagination), + }); + }); +}; diff --git a/resources/scripts/api/admin/mounts/updateMount.ts b/resources/scripts/api/admin/mounts/updateMount.ts new file mode 100644 index 0000000000..c2485777a0 --- /dev/null +++ b/resources/scripts/api/admin/mounts/updateMount.ts @@ -0,0 +1,12 @@ +import http from '@/api/http'; +import { Mount, rawDataToMount } from '@/api/admin/mounts/getMounts'; + +export default (id: number, name: string, description: string | null, source: string, target: string, readOnly: boolean, userMountable: boolean, include: string[] = []): Promise => { + return new Promise((resolve, reject) => { + http.patch(`/api/application/mounts/${id}`, { + name, description, source, target, read_only: readOnly, user_mountable: userMountable, + }, { params: { include: include.join(',') } }) + .then(({ data }) => resolve(rawDataToMount(data))) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/admin/nest.ts b/resources/scripts/api/admin/nest.ts new file mode 100644 index 0000000000..697b15bedf --- /dev/null +++ b/resources/scripts/api/admin/nest.ts @@ -0,0 +1,25 @@ +import { Model, UUID } from '@/api/admin/index'; +import { Egg } from '@/api/admin/egg'; +import http, { QueryBuilderParams, withQueryBuilderParams } from '@/api/http'; +import { Transformers } from '@definitions/admin'; + +export interface Nest extends Model { + id: number; + uuid: UUID; + author: string; + name: string; + description?: string; + createdAt: Date; + updatedAt: Date; + relationships: { + eggs?: Egg[]; + }; +} + +export const searchNests = async (params: QueryBuilderParams<'name'>): Promise => { + const { data } = await http.get('/api/application/nests', { + params: withQueryBuilderParams(params), + }); + + return data.data.map(Transformers.toNest); +}; diff --git a/resources/scripts/api/admin/nests/createNest.ts b/resources/scripts/api/admin/nests/createNest.ts new file mode 100644 index 0000000000..6f8d045fa3 --- /dev/null +++ b/resources/scripts/api/admin/nests/createNest.ts @@ -0,0 +1,12 @@ +import http from '@/api/http'; +import { Nest, rawDataToNest } from '@/api/admin/nests/getNests'; + +export default (name: string, description: string | null, include: string[] = []): Promise => { + return new Promise((resolve, reject) => { + http.post('/api/application/nests', { + name, description, + }, { params: { include: include.join(',') } }) + .then(({ data }) => resolve(rawDataToNest(data))) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/admin/nests/deleteNest.ts b/resources/scripts/api/admin/nests/deleteNest.ts new file mode 100644 index 0000000000..d6d4ae3a56 --- /dev/null +++ b/resources/scripts/api/admin/nests/deleteNest.ts @@ -0,0 +1,9 @@ +import http from '@/api/http'; + +export default (id: number): Promise => { + return new Promise((resolve, reject) => { + http.delete(`/api/application/nests/${id}`) + .then(() => resolve()) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/admin/nests/getEggs.ts b/resources/scripts/api/admin/nests/getEggs.ts new file mode 100644 index 0000000000..6a406dc969 --- /dev/null +++ b/resources/scripts/api/admin/nests/getEggs.ts @@ -0,0 +1,38 @@ +import http, { getPaginationSet, PaginatedResult } from '@/api/http'; +import { useContext } from 'react'; +import useSWR from 'swr'; +import { createContext } from '@/api/admin'; +import { Egg, rawDataToEgg } from '@/api/admin/eggs/getEgg'; + +export interface Filters { + id?: string; + name?: string; +} + +export const Context = createContext(); + +export default (nestId: number, include: string[] = []) => { + const { page, filters, sort, sortDirection } = useContext(Context); + + const params = {}; + if (filters !== null) { + Object.keys(filters).forEach(key => { + // @ts-ignore + params['filter[' + key + ']'] = filters[key]; + }); + } + + if (sort !== null) { + // @ts-ignore + params.sort = (sortDirection ? '-' : '') + sort; + } + + return useSWR>([ nestId, 'eggs', page, filters, sort, sortDirection ], async () => { + const { data } = await http.get(`/api/application/nests/${nestId}/eggs`, { params: { include: include.join(','), page, ...params } }); + + return ({ + items: (data.data || []).map(rawDataToEgg), + pagination: getPaginationSet(data.meta.pagination), + }); + }); +}; diff --git a/resources/scripts/api/admin/nests/getNest.ts b/resources/scripts/api/admin/nests/getNest.ts new file mode 100644 index 0000000000..23f1cf782f --- /dev/null +++ b/resources/scripts/api/admin/nests/getNest.ts @@ -0,0 +1,10 @@ +import http from '@/api/http'; +import { Nest, rawDataToNest } from '@/api/admin/nests/getNests'; + +export default (id: number, include: string[]): Promise => { + return new Promise((resolve, reject) => { + http.get(`/api/application/nests/${id}`, { params: { include: include.join(',') } }) + .then(({ data }) => resolve(rawDataToNest(data))) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/admin/nests/getNests.ts b/resources/scripts/api/admin/nests/getNests.ts new file mode 100644 index 0000000000..712a2a29be --- /dev/null +++ b/resources/scripts/api/admin/nests/getNests.ts @@ -0,0 +1,66 @@ +import http, { FractalResponseData, FractalResponseList, getPaginationSet, PaginatedResult } from '@/api/http'; +import { useContext } from 'react'; +import useSWR from 'swr'; +import { createContext } from '@/api/admin'; +import { Egg, rawDataToEgg } from '@/api/admin/eggs/getEgg'; + +export interface Nest { + id: number; + uuid: string; + author: string; + name: string; + description?: string; + createdAt: Date; + updatedAt: Date; + + relations: { + eggs: Egg[] | undefined; + }, +} + +export const rawDataToNest = ({ attributes }: FractalResponseData): Nest => ({ + id: attributes.id, + uuid: attributes.uuid, + author: attributes.author, + name: attributes.name, + description: attributes.description, + createdAt: new Date(attributes.created_at), + updatedAt: new Date(attributes.updated_at), + + relations: { + eggs: ((attributes.relationships?.eggs as FractalResponseList | undefined)?.data || []).map(rawDataToEgg), + }, +}); + +export interface Filters { + id?: string; + name?: string; +} + +export const Context = createContext(); + +export default (include: string[] = []) => { + const { page, filters, sort, sortDirection } = useContext(Context); + + const params = {}; + if (filters !== null) { + Object.keys(filters).forEach(key => { + // @ts-ignore + params['filter[' + key + ']'] = filters[key]; + }); + } + + if (sort !== null) { + // @ts-ignore + params.sort = (sortDirection ? '-' : '') + sort; + } + + return useSWR>([ 'nests', page, filters, sort, sortDirection ], async () => { + const { data } = await http.get('/api/application/nests', { params: { include: include.join(','), page, ...params } }); + + return ({ + items: (data.data || []).map(rawDataToNest), + pagination: getPaginationSet(data.meta.pagination), + }); + }); +}; diff --git a/resources/scripts/api/admin/nests/importEgg.ts b/resources/scripts/api/admin/nests/importEgg.ts new file mode 100644 index 0000000000..2163386ca5 --- /dev/null +++ b/resources/scripts/api/admin/nests/importEgg.ts @@ -0,0 +1,17 @@ +import http from '@/api/http'; +import { Egg, rawDataToEgg } from '@/api/admin/eggs/getEgg'; + +export default (id: number, content: any, type = 'application/json', include: string[] = []): Promise => { + return new Promise((resolve, reject) => { + http.post(`/api/application/nests/${id}/import`, content, { + headers: { + 'Content-Type': type, + }, + params: { + include: include.join(','), + }, + }) + .then(({ data }) => resolve(rawDataToEgg(data))) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/admin/nests/updateNest.ts b/resources/scripts/api/admin/nests/updateNest.ts new file mode 100644 index 0000000000..869f0532b6 --- /dev/null +++ b/resources/scripts/api/admin/nests/updateNest.ts @@ -0,0 +1,12 @@ +import http from '@/api/http'; +import { Nest, rawDataToNest } from '@/api/admin/nests/getNests'; + +export default (id: number, name: string, description: string | null, include: string[] = []): Promise => { + return new Promise((resolve, reject) => { + http.patch(`/api/application/nests/${id}`, { + name, description, + }, { params: { include: include.join(',') } }) + .then(({ data }) => resolve(rawDataToNest(data))) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/admin/node.ts b/resources/scripts/api/admin/node.ts new file mode 100644 index 0000000000..dae7a09cc7 --- /dev/null +++ b/resources/scripts/api/admin/node.ts @@ -0,0 +1,84 @@ +import { Model, UUID, WithRelationships, withRelationships } from '@/api/admin/index'; +import { Location } from '@/api/admin/location'; +import http, { QueryBuilderParams, withQueryBuilderParams } from '@/api/http'; +import { Transformers } from '@definitions/admin'; +import { Server } from '@/api/admin/server'; + +interface NodePorts { + http: { + listen: number; + public: number; + }; + sftp: { + listen: number; + public: number; + }; +} + +export interface Allocation extends Model { + id: number; + ip: string; + port: number; + alias: string | null; + isAssigned: boolean; + relationships: { + node?: Node; + server?: Server | null; + }; + getDisplayText(): string; +} + +export interface Node extends Model { + id: number; + uuid: UUID; + isPublic: boolean; + locationId: number; + databaseHostId: number; + name: string; + description: string | null; + fqdn: string; + ports: NodePorts; + scheme: 'http' | 'https'; + isBehindProxy: boolean; + isMaintenanceMode: boolean; + memory: number; + memoryOverallocate: number; + disk: number; + diskOverallocate: number; + uploadSize: number; + daemonBase: string; + createdAt: Date; + updatedAt: Date; + relationships: { + location?: Location; + }; +} + +/** + * Gets a single node and returns it. + */ +export const getNode = async (id: string | number): Promise> => { + const { data } = await http.get(`/api/application/nodes/${id}`, { + params: { + include: [ 'location' ], + }, + }); + + return withRelationships(Transformers.toNode(data.data), 'location'); +}; + +export const searchNodes = async (params: QueryBuilderParams<'name'>): Promise => { + const { data } = await http.get('/api/application/nodes', { + params: withQueryBuilderParams(params), + }); + + return data.data.map(Transformers.toNode); +}; + +export const getAllocations = async (id: string | number, params?: QueryBuilderParams<'ip' | 'server_id'>): Promise => { + const { data } = await http.get(`/api/application/nodes/${id}/allocations`, { + params: withQueryBuilderParams(params), + }); + + return data.data.map(Transformers.toAllocation); +}; diff --git a/resources/scripts/api/admin/nodes/allocations/createAllocation.ts b/resources/scripts/api/admin/nodes/allocations/createAllocation.ts new file mode 100644 index 0000000000..89bacbd4d7 --- /dev/null +++ b/resources/scripts/api/admin/nodes/allocations/createAllocation.ts @@ -0,0 +1,16 @@ +import http from '@/api/http'; +import { Allocation, rawDataToAllocation } from '@/api/admin/nodes/getAllocations'; + +export interface Values { + ip: string; + ports: number[]; + alias?: string; +} + +export default (id: string | number, values: Values, include: string[] = []): Promise => { + return new Promise((resolve, reject) => { + http.post(`/api/application/nodes/${id}/allocations`, values, { params: { include: include.join(',') } }) + .then(({ data }) => resolve((data || []).map(rawDataToAllocation))) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/admin/nodes/allocations/deleteAllocation.ts b/resources/scripts/api/admin/nodes/allocations/deleteAllocation.ts new file mode 100644 index 0000000000..f4a7751834 --- /dev/null +++ b/resources/scripts/api/admin/nodes/allocations/deleteAllocation.ts @@ -0,0 +1,9 @@ +import http from '@/api/http'; + +export default (nodeId: number, allocationId: number): Promise => { + return new Promise((resolve, reject) => { + http.delete(`/api/application/nodes/${nodeId}/allocations/${allocationId}`) + .then(() => resolve()) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/admin/nodes/allocations/getAllocations.ts b/resources/scripts/api/admin/nodes/allocations/getAllocations.ts new file mode 100644 index 0000000000..14c99c6555 --- /dev/null +++ b/resources/scripts/api/admin/nodes/allocations/getAllocations.ts @@ -0,0 +1,39 @@ +import { Allocation, rawDataToAllocation } from '@/api/admin/nodes/getAllocations'; +import http, { getPaginationSet, PaginatedResult } from '@/api/http'; +import { useContext } from 'react'; +import useSWR from 'swr'; +import { createContext } from '@/api/admin'; + +export interface Filters { + id?: string; + ip?: string; + port?: string; +} + +export const Context = createContext(); + +export default (id: number, include: string[] = []) => { + const { page, filters, sort, sortDirection } = useContext(Context); + + const params = {}; + if (filters !== null) { + Object.keys(filters).forEach(key => { + // @ts-ignore + params['filter[' + key + ']'] = filters[key]; + }); + } + + if (sort !== null) { + // @ts-ignore + params.sort = (sortDirection ? '-' : '') + sort; + } + + return useSWR>([ 'allocations', page, filters, sort, sortDirection ], async () => { + const { data } = await http.get(`/api/application/nodes/${id}/allocations`, { params: { include: include.join(','), page, ...params } }); + + return ({ + items: (data.data || []).map(rawDataToAllocation), + pagination: getPaginationSet(data.meta.pagination), + }); + }); +}; diff --git a/resources/scripts/api/admin/nodes/createNode.ts b/resources/scripts/api/admin/nodes/createNode.ts new file mode 100644 index 0000000000..8143a92c32 --- /dev/null +++ b/resources/scripts/api/admin/nodes/createNode.ts @@ -0,0 +1,42 @@ +import http from '@/api/http'; +import { Node, rawDataToNode } from '@/api/admin/nodes/getNodes'; + +export interface Values { + name: string; + locationId: number; + databaseHostId: number | null; + fqdn: string; + scheme: string; + behindProxy: boolean; + public: boolean; + daemonBase: string; + + memory: number; + memoryOverallocate: number; + disk: number; + diskOverallocate: number; + + listenPortHTTP: number; + publicPortHTTP: number; + listenPortSFTP: number; + publicPortSFTP: number; +} + +export default (values: Values, include: string[] = []): Promise => { + const data = {}; + + Object.keys(values).forEach((key) => { + const key2 = key + .replace('HTTP', 'Http') + .replace('SFTP', 'Sftp') + .replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`); + // @ts-ignore + data[key2] = values[key]; + }); + + return new Promise((resolve, reject) => { + http.post('/api/application/nodes', data, { params: { include: include.join(',') } }) + .then(({ data }) => resolve(rawDataToNode(data))) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/admin/nodes/deleteNode.ts b/resources/scripts/api/admin/nodes/deleteNode.ts new file mode 100644 index 0000000000..56a4261118 --- /dev/null +++ b/resources/scripts/api/admin/nodes/deleteNode.ts @@ -0,0 +1,9 @@ +import http from '@/api/http'; + +export default (id: number): Promise => { + return new Promise((resolve, reject) => { + http.delete(`/api/application/nodes/${id}`) + .then(() => resolve()) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/admin/nodes/getAllocations.ts b/resources/scripts/api/admin/nodes/getAllocations.ts new file mode 100644 index 0000000000..e6f773dfef --- /dev/null +++ b/resources/scripts/api/admin/nodes/getAllocations.ts @@ -0,0 +1,61 @@ +import http, { FractalResponseData } from '@/api/http'; +import { rawDataToServer, Server } from '@/api/admin/servers/getServers'; + +export interface Allocation { + id: number; + ip: string; + port: number; + alias: string | null; + serverId: number | null; + assigned: boolean; + + relations: { + server?: Server; + } + + getDisplayText (): string; +} + +export const rawDataToAllocation = ({ attributes }: FractalResponseData): Allocation => ({ + id: attributes.id, + ip: attributes.ip, + port: attributes.port, + alias: attributes.alias || null, + serverId: attributes.server_id, + assigned: attributes.assigned, + + relations: { + server: attributes.relationships?.server?.object === 'server' ? rawDataToServer(attributes.relationships.server as FractalResponseData) : undefined, + }, + + // TODO: If IP is an IPv6, wrap IP in []. + getDisplayText (): string { + if (attributes.alias !== null) { + return `${attributes.ip}:${attributes.port} (${attributes.alias})`; + } + return `${attributes.ip}:${attributes.port}`; + }, +}); + +export interface Filters { + ip?: string + /* eslint-disable camelcase */ + server_id?: string; + /* eslint-enable camelcase */ +} + +export default (id: string | number, filters: Filters = {}, include: string[] = []): Promise => { + const params = {}; + if (filters !== null) { + Object.keys(filters).forEach(key => { + // @ts-ignore + params['filter[' + key + ']'] = filters[key]; + }); + } + + return new Promise((resolve, reject) => { + http.get(`/api/application/nodes/${id}/allocations`, { params: { include: include.join(','), ...params } }) + .then(({ data }) => resolve((data.data || []).map(rawDataToAllocation))) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/admin/nodes/getNode.ts b/resources/scripts/api/admin/nodes/getNode.ts new file mode 100644 index 0000000000..b940c8e2eb --- /dev/null +++ b/resources/scripts/api/admin/nodes/getNode.ts @@ -0,0 +1,10 @@ +import http from '@/api/http'; +import { Node, rawDataToNode } from '@/api/admin/nodes/getNodes'; + +export default (id: number, include: string[] = []): Promise => { + return new Promise((resolve, reject) => { + http.get(`/api/application/nodes/${id}`, { params: { include: include.join(',') } }) + .then(({ data }) => resolve(rawDataToNode(data))) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/admin/nodes/getNodeConfiguration.ts b/resources/scripts/api/admin/nodes/getNodeConfiguration.ts new file mode 100644 index 0000000000..f439b1e7e9 --- /dev/null +++ b/resources/scripts/api/admin/nodes/getNodeConfiguration.ts @@ -0,0 +1,9 @@ +import http from '@/api/http'; + +export default (id: number): Promise => { + return new Promise((resolve, reject) => { + http.get(`/api/application/nodes/${id}/configuration?format=yaml`) + .then(({ data }) => resolve(data)) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/admin/nodes/getNodeInformation.ts b/resources/scripts/api/admin/nodes/getNodeInformation.ts new file mode 100644 index 0000000000..771433a5eb --- /dev/null +++ b/resources/scripts/api/admin/nodes/getNodeInformation.ts @@ -0,0 +1,19 @@ +import http from '@/api/http'; + +export interface NodeInformation { + version: string; + system: { + type: string; + arch: string; + release: string; + cpus: number; + }; +} + +export default (id: number): Promise => { + return new Promise((resolve, reject) => { + http.get(`/api/application/nodes/${id}/information`) + .then(({ data }) => resolve(data)) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/admin/nodes/getNodes.ts b/resources/scripts/api/admin/nodes/getNodes.ts new file mode 100644 index 0000000000..59e87ceb42 --- /dev/null +++ b/resources/scripts/api/admin/nodes/getNodes.ts @@ -0,0 +1,107 @@ +import http, { FractalResponseData, getPaginationSet, PaginatedResult } from '@/api/http'; +import { useContext } from 'react'; +import useSWR from 'swr'; +import { createContext } from '@/api/admin'; +import { Database, rawDataToDatabase } from '@/api/admin/databases/getDatabases'; +import { Location, rawDataToLocation } from '@/api/admin/locations/getLocations'; + +export interface Node { + id: number; + uuid: string; + public: boolean; + name: string; + description: string | null; + locationId: number; + databaseHostId: number | null; + fqdn: string; + listenPortHTTP: number; + publicPortHTTP: number; + listenPortSFTP: number; + publicPortSFTP: number; + scheme: string; + behindProxy: boolean; + maintenanceMode: boolean; + memory: number; + memoryOverallocate: number; + disk: number; + diskOverallocate: number; + uploadSize: number; + daemonBase: string; + createdAt: Date; + updatedAt: Date; + + relations: { + databaseHost: Database | undefined; + location: Location | undefined; + } +} + +export const rawDataToNode = ({ attributes }: FractalResponseData): Node => ({ + id: attributes.id, + uuid: attributes.uuid, + public: attributes.public, + name: attributes.name, + description: attributes.description, + locationId: attributes.location_id, + databaseHostId: attributes.database_host_id, + fqdn: attributes.fqdn, + listenPortHTTP: attributes.listen_port_http, + publicPortHTTP: attributes.public_port_http, + listenPortSFTP: attributes.listen_port_sftp, + publicPortSFTP: attributes.public_port_sftp, + scheme: attributes.scheme, + behindProxy: attributes.behind_proxy, + maintenanceMode: attributes.maintenance_mode, + memory: attributes.memory, + memoryOverallocate: attributes.memory_overallocate, + disk: attributes.disk, + diskOverallocate: attributes.disk_overallocate, + uploadSize: attributes.upload_size, + daemonBase: attributes.daemon_base, + createdAt: new Date(attributes.created_at), + updatedAt: new Date(attributes.updated_at), + + relations: { + // eslint-disable-next-line camelcase + databaseHost: attributes.relationships?.database_host !== undefined && attributes.relationships?.database_host.object !== 'null_resource' ? rawDataToDatabase(attributes.relationships.database_host as FractalResponseData) : undefined, + location: attributes.relationships?.location !== undefined ? rawDataToLocation(attributes.relationships.location as FractalResponseData) : undefined, + }, +}); + +export interface Filters { + id?: string; + uuid?: string; + name?: string; + image?: string; + /* eslint-disable camelcase */ + external_id?: string; + /* eslint-enable camelcase */ +} + +export const Context = createContext(); + +export default (include: string[] = []) => { + const { page, filters, sort, sortDirection } = useContext(Context); + + const params = {}; + if (filters !== null) { + Object.keys(filters).forEach(key => { + // @ts-ignore + params['filter[' + key + ']'] = filters[key]; + }); + } + + if (sort !== null) { + // @ts-ignore + params.sort = (sortDirection ? '-' : '') + sort; + } + + return useSWR>([ 'nodes', page, filters, sort, sortDirection ], async () => { + const { data } = await http.get('/api/application/nodes', { params: { include: include.join(','), page, ...params } }); + + return ({ + items: (data.data || []).map(rawDataToNode), + pagination: getPaginationSet(data.meta.pagination), + }); + }); +}; diff --git a/resources/scripts/api/admin/nodes/updateNode.ts b/resources/scripts/api/admin/nodes/updateNode.ts new file mode 100644 index 0000000000..623f66931f --- /dev/null +++ b/resources/scripts/api/admin/nodes/updateNode.ts @@ -0,0 +1,21 @@ +import http from '@/api/http'; +import { Node, rawDataToNode } from '@/api/admin/nodes/getNodes'; + +export default (id: number, node: Partial, include: string[] = []): Promise => { + const data = {}; + + Object.keys(node).forEach((key) => { + const key2 = key + .replace('HTTP', 'Http') + .replace('SFTP', 'Sftp') + .replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`); + // @ts-ignore + data[key2] = node[key]; + }); + + return new Promise((resolve, reject) => { + http.patch(`/api/application/nodes/${id}`, data, { params: { include: include.join(',') } }) + .then(({ data }) => resolve(rawDataToNode(data))) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/admin/roles.ts b/resources/scripts/api/admin/roles.ts new file mode 100644 index 0000000000..8fb5c4c850 --- /dev/null +++ b/resources/scripts/api/admin/roles.ts @@ -0,0 +1,103 @@ +import http, { getPaginationSet, PaginatedResult } from '@/api/http'; +import { Transformers, UserRole } from '@definitions/admin'; +import { useContext } from 'react'; +import useSWR from 'swr'; +import { createContext } from '@/api/admin/index'; + +export interface Filters { + id?: string; + name?: string; +} + +export const Context = createContext(); + +const createRole = (name: string, description: string | null, include: string[] = []): Promise => { + return new Promise((resolve, reject) => { + http.post('/api/application/roles', { + name, description, + }, { params: { include: include.join(',') } }) + .then(({ data }) => resolve(Transformers.toUserRole(data))) + .catch(reject); + }); +}; + +const deleteRole = (id: number): Promise => { + return new Promise((resolve, reject) => { + http.delete(`/api/application/roles/${id}`) + .then(() => resolve()) + .catch(reject); + }); +}; + +const getRole = (id: number, include: string[] = []): Promise => { + return new Promise((resolve, reject) => { + http.get(`/api/application/roles/${id}`, { params: { include: include.join(',') } }) + .then(({ data }) => resolve(Transformers.toUserRole(data))) + .catch(reject); + }); +}; + +const searchRoles = (filters?: { name?: string }): Promise => { + const params = {}; + if (filters !== undefined) { + Object.keys(filters).forEach(key => { + // @ts-ignore + params['filter[' + key + ']'] = filters[key]; + }); + } + + return new Promise((resolve, reject) => { + http.get('/api/application/roles', { params }) + .then(response => resolve( + (response.data.data || []).map(Transformers.toUserRole) + )) + .catch(reject); + }); +}; + +const updateRole = (id: number, name: string, description: string | null, include: string[] = []): Promise => { + return new Promise((resolve, reject) => { + http.patch(`/api/application/roles/${id}`, { + name, description, + }, { params: { include: include.join(',') } }) + .then(({ data }) => resolve(Transformers.toUserRole(data))) + .catch(reject); + }); +}; + +const getRoles = (include: string[] = []) => { + // eslint-disable-next-line react-hooks/rules-of-hooks + const { page, filters, sort, sortDirection } = useContext(Context); + + const params = {}; + if (filters !== null) { + Object.keys(filters).forEach(key => { + // @ts-ignore + params['filter[' + key + ']'] = filters[key]; + }); + } + + if (sort !== null) { + // @ts-ignore + params.sort = (sortDirection ? '-' : '') + sort; + } + + // eslint-disable-next-line react-hooks/rules-of-hooks + return useSWR>([ 'roles', page, filters, sort, sortDirection ], async () => { + const { data } = await http.get('/api/application/roles', { params: { include: include.join(','), page, ...params } }); + + return ({ + items: (data.data || []).map(Transformers.toUserRole), + pagination: getPaginationSet(data.meta.pagination), + }); + }); +}; + +export { + createRole, + deleteRole, + getRole, + searchRoles, + updateRole, + getRoles, +}; diff --git a/resources/scripts/api/admin/server.ts b/resources/scripts/api/admin/server.ts new file mode 100644 index 0000000000..26f9ba6b54 --- /dev/null +++ b/resources/scripts/api/admin/server.ts @@ -0,0 +1,99 @@ +import useSWR, { SWRResponse } from 'swr'; +import { AxiosError } from 'axios'; +import { useParams } from 'react-router-dom'; +import http from '@/api/http'; +import { Model, UUID, withRelationships, WithRelationships } from '@/api/admin/index'; +import { Allocation, Node } from '@/api/admin/node'; +import { Transformers, User } from '@definitions/admin'; +import { Egg, EggVariable } from '@/api/admin/egg'; +import { Nest } from '@/api/admin/nest'; + +/** + * Defines the limits for a server that exists on the Panel. + */ +interface ServerLimits { + memory: number; + swap: number; + disk: number; + io: number; + cpu: number; + threads: string | null; + oomDisabled: boolean; +} + +export interface ServerVariable extends EggVariable { + serverValue: string; +} + +/** + * Defines a single server instance that is returned from the Panel's admin + * API endpoints. + */ +export interface Server extends Model { + id: number; + uuid: UUID; + externalId: string | null; + identifier: string; + name: string; + description: string; + status: string; + userId: number; + nodeId: number; + allocationId: number; + eggId: number; + nestId: number; + limits: ServerLimits; + featureLimits: { + databases: number; + allocations: number; + backups: number; + }; + container: { + startup: string | null; + image: string; + environment: Record; + }; + createdAt: Date; + updatedAt: Date; + relationships: { + allocations?: Allocation[]; + nest?: Nest; + egg?: Egg; + node?: Node; + user?: User; + variables?: ServerVariable[]; + }; +} + +/** + * A standard API response with the minimum viable details for the frontend + * to correctly render a server. + */ +type LoadedServer = WithRelationships; + +/** + * Fetches a server from the API and ensures that the allocations, user, and + * node data is loaded. + */ +export const getServer = async (id: number | string): Promise => { + const { data } = await http.get(`/api/application/servers/${id}`, { + params: { + include: ['allocations', 'user', 'node', 'variables'], + }, + }); + + return withRelationships(Transformers.toServer(data), 'allocations', 'user', 'node', 'variables'); +}; + +/** + * Returns an SWR instance by automatically loading in the server for the currently + * loaded route match in the admin area. + */ +export const useServerFromRoute = (): SWRResponse => { + const params = useParams<'id'>(); + + return useSWR(`/api/application/servers/${params.id}`, async () => getServer(Number(params.id)), { + revalidateOnMount: false, + revalidateOnFocus: false, + }); +}; diff --git a/resources/scripts/api/admin/servers/createServer.ts b/resources/scripts/api/admin/servers/createServer.ts new file mode 100644 index 0000000000..3fd94ca620 --- /dev/null +++ b/resources/scripts/api/admin/servers/createServer.ts @@ -0,0 +1,80 @@ +import http from '@/api/http'; +import { Server, rawDataToServer } from '@/api/admin/servers/getServers'; + +export interface CreateServerRequest { + externalId: string; + name: string; + description: string | null; + ownerId: number; + nodeId: number; + + limits: { + memory: number; + swap: number; + disk: number; + io: number; + cpu: number; + threads: string; + oomDisabled: boolean; + } + + featureLimits: { + allocations: number; + backups: number; + databases: number; + }; + + allocation: { + default: number; + additional: number[]; + }; + + startup: string; + environment: Record; + eggId: number; + image: string; + skipScripts: boolean; + startOnCompletion: boolean; +} + +export default (r: CreateServerRequest, include: string[] = []): Promise => { + return new Promise((resolve, reject) => { + http.post('/api/application/servers', { + externalId: r.externalId, + name: r.name, + description: r.description, + owner_id: r.ownerId, + node_id: r.nodeId, + + limits: { + cpu: r.limits.cpu, + disk: r.limits.disk, + io: r.limits.io, + memory: r.limits.memory, + swap: r.limits.swap, + threads: r.limits.threads, + oom_killer: r.limits.oomDisabled, + }, + + feature_limits: { + allocations: r.featureLimits.allocations, + backups: r.featureLimits.backups, + databases: r.featureLimits.databases, + }, + + allocation: { + default: r.allocation.default, + additional: r.allocation.additional, + }, + + startup: r.startup, + environment: r.environment, + egg_id: r.eggId, + image: r.image, + skip_scripts: r.skipScripts, + start_on_completion: r.startOnCompletion, + }, { params: { include: include.join(',') } }) + .then(({ data }) => resolve(rawDataToServer(data))) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/admin/servers/deleteServer.ts b/resources/scripts/api/admin/servers/deleteServer.ts new file mode 100644 index 0000000000..5795595182 --- /dev/null +++ b/resources/scripts/api/admin/servers/deleteServer.ts @@ -0,0 +1,9 @@ +import http from '@/api/http'; + +export default (id: number): Promise => { + return new Promise((resolve, reject) => { + http.delete(`/api/application/servers/${id}`) + .then(() => resolve()) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/admin/servers/getServer.ts b/resources/scripts/api/admin/servers/getServer.ts new file mode 100644 index 0000000000..be10f0216d --- /dev/null +++ b/resources/scripts/api/admin/servers/getServer.ts @@ -0,0 +1,10 @@ +import http from '@/api/http'; +import { Server, rawDataToServer } from '@/api/admin/servers/getServers'; + +export default (id: number, include: string[]): Promise => { + return new Promise((resolve, reject) => { + http.get(`/api/application/servers/${id}`, { params: { include: include.join(',') } }) + .then(({ data }) => resolve(rawDataToServer(data))) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/admin/servers/getServers.ts b/resources/scripts/api/admin/servers/getServers.ts new file mode 100644 index 0000000000..6d73f070b4 --- /dev/null +++ b/resources/scripts/api/admin/servers/getServers.ts @@ -0,0 +1,177 @@ +import { Allocation, rawDataToAllocation } from '@/api/admin/nodes/getAllocations'; +import { useContext } from 'react'; +import useSWR from 'swr'; +import { createContext } from '@/api/admin'; +import http, { FractalResponseData, FractalResponseList, getPaginationSet, PaginatedResult } from '@/api/http'; +import { Egg, rawDataToEgg } from '@/api/admin/eggs/getEgg'; +import { Node, rawDataToNode } from '@/api/admin/nodes/getNodes'; +import { Transformers, User } from '@definitions/admin'; + +export interface ServerVariable { + id: number; + eggId: number; + name: string; + description: string; + envVariable: string; + defaultValue: string; + userViewable: boolean; + userEditable: boolean; + rules: string; + required: boolean; + serverValue: string; + createdAt: Date; + updatedAt: Date; +} + +export const rawDataToServerVariable = ({ attributes }: FractalResponseData): ServerVariable => ({ + id: attributes.id, + eggId: attributes.egg_id, + name: attributes.name, + description: attributes.description, + envVariable: attributes.env_variable, + defaultValue: attributes.default_value, + userViewable: attributes.user_viewable, + userEditable: attributes.user_editable, + rules: attributes.rules, + required: attributes.required, + serverValue: attributes.server_value, + createdAt: new Date(attributes.created_at), + updatedAt: new Date(attributes.updated_at), +}); + +export interface Server { + id: number; + externalId: string | null + uuid: string; + identifier: string; + name: string; + description: string; + status: string; + + limits: { + memory: number; + swap: number; + disk: number; + io: number; + cpu: number; + threads: string | null; + oomDisabled: boolean; + } + + featureLimits: { + databases: number; + allocations: number; + backups: number; + } + + ownerId: number; + nodeId: number; + allocationId: number; + nestId: number; + eggId: number; + + container: { + startup: string; + image: string; + environment: Map; + } + + createdAt: Date; + updatedAt: Date; + + relations: { + allocations?: Allocation[]; + egg?: Egg; + node?: Node; + user?: User; + variables: ServerVariable[]; + } +} + +export const rawDataToServer = ({ attributes }: FractalResponseData): Server => ({ + id: attributes.id, + externalId: attributes.external_id, + uuid: attributes.uuid, + identifier: attributes.identifier, + name: attributes.name, + description: attributes.description, + status: attributes.status, + + limits: { + memory: attributes.limits.memory, + swap: attributes.limits.swap, + disk: attributes.limits.disk, + io: attributes.limits.io, + cpu: attributes.limits.cpu, + threads: attributes.limits.threads, + oomDisabled: attributes.limits.oom_disabled, + }, + + featureLimits: { + databases: attributes.feature_limits.databases, + allocations: attributes.feature_limits.allocations, + backups: attributes.feature_limits.backups, + }, + + ownerId: attributes.owner_id, + nodeId: attributes.node_id, + allocationId: attributes.allocation_id, + nestId: attributes.nest_id, + eggId: attributes.egg_id, + + container: { + startup: attributes.container.startup, + image: attributes.container.image, + environment: attributes.container.environment, + }, + + createdAt: new Date(attributes.created_at), + updatedAt: new Date(attributes.updated_at), + + relations: { + allocations: ((attributes.relationships?.allocations as FractalResponseList | undefined)?.data || []).map(rawDataToAllocation), + egg: attributes.relationships?.egg?.object === 'egg' ? rawDataToEgg(attributes.relationships.egg as FractalResponseData) : undefined, + node: attributes.relationships?.node?.object === 'node' ? rawDataToNode(attributes.relationships.node as FractalResponseData) : undefined, + user: attributes.relationships?.user?.object === 'user' ? Transformers.toUser(attributes.relationships.user as FractalResponseData) : undefined, + variables: ((attributes.relationships?.variables as FractalResponseList | undefined)?.data || []).map(rawDataToServerVariable), + }, +}) as Server; + +export interface Filters { + id?: string; + uuid?: string; + name?: string; + /* eslint-disable camelcase */ + owner_id?: string; + node_id?: string; + external_id?: string; + /* eslint-enable camelcase */ +} + +export const Context = createContext(); + +export default (include: string[] = []) => { + const { page, filters, sort, sortDirection } = useContext(Context); + + const params = {}; + if (filters !== null) { + Object.keys(filters).forEach(key => { + // @ts-ignore + params['filter[' + key + ']'] = filters[key]; + }); + } + + if (sort !== null) { + // @ts-ignore + params.sort = (sortDirection ? '-' : '') + sort; + } + + return useSWR>([ 'servers', page, filters, sort, sortDirection ], async () => { + const { data } = await http.get('/api/application/servers', { params: { include: include.join(','), page, ...params } }); + + return ({ + items: (data.data || []).map(rawDataToServer), + pagination: getPaginationSet(data.meta.pagination), + }); + }); +}; diff --git a/resources/scripts/api/admin/servers/updateServer.ts b/resources/scripts/api/admin/servers/updateServer.ts new file mode 100644 index 0000000000..e74b7422ae --- /dev/null +++ b/resources/scripts/api/admin/servers/updateServer.ts @@ -0,0 +1,64 @@ +import http from '@/api/http'; +import { Server, rawDataToServer } from '@/api/admin/servers/getServers'; + +export interface Values { + externalId: string; + name: string; + ownerId: number; + + limits: { + memory: number; + swap: number; + disk: number; + io: number; + cpu: number; + threads: string; + oomDisabled: boolean; + } + + featureLimits: { + allocations: number; + backups: number; + databases: number; + } + + allocationId: number; + addAllocations: number[]; + removeAllocations: number[]; +} + +export default (id: number, server: Partial, include: string[] = []): Promise => { + return new Promise((resolve, reject) => { + http.patch( + `/api/application/servers/${id}`, + { + external_id: server.externalId, + name: server.name, + owner_id: server.ownerId, + + limits: { + memory: server.limits?.memory, + swap: server.limits?.swap, + disk: server.limits?.disk, + io: server.limits?.io, + cpu: server.limits?.cpu, + threads: server.limits?.threads, + oom_killer: server.limits?.oomDisabled, + }, + + feature_limits: { + allocations: server.featureLimits?.allocations, + backups: server.featureLimits?.backups, + databases: server.featureLimits?.databases, + }, + + allocation_id: server.allocationId, + add_allocations: server.addAllocations, + remove_allocations: server.removeAllocations, + }, + { params: { include: include.join(',') } } + ) + .then(({ data }) => resolve(rawDataToServer(data))) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/admin/servers/updateServerStartup.ts b/resources/scripts/api/admin/servers/updateServerStartup.ts new file mode 100644 index 0000000000..7dec26e30c --- /dev/null +++ b/resources/scripts/api/admin/servers/updateServerStartup.ts @@ -0,0 +1,28 @@ +import http from '@/api/http'; +import { Server, rawDataToServer } from '@/api/admin/servers/getServers'; + +export interface Values { + startup: string; + environment: Record; + eggId: number; + image: string; + skipScripts: boolean; +} + +export default (id: number, values: Partial, include: string[] = []): Promise => { + return new Promise((resolve, reject) => { + http.patch( + `/api/application/servers/${id}/startup`, + { + startup: values.startup !== '' ? values.startup : null, + environment: values.environment, + egg_id: values.eggId, + image: values.image, + skip_scripts: values.skipScripts, + }, + { params: { include: include.join(',') } } + ) + .then(({ data }) => resolve(rawDataToServer(data))) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/admin/users.ts b/resources/scripts/api/admin/users.ts new file mode 100644 index 0000000000..817721cb36 --- /dev/null +++ b/resources/scripts/api/admin/users.ts @@ -0,0 +1,96 @@ +import http, { + FractalPaginatedResponse, + PaginatedResult, + QueryBuilderParams, + getPaginationSet, + withQueryBuilderParams, +} from '@/api/http'; +import { Transformers, User } from '@definitions/admin'; +import useSWR, { SWRConfiguration, SWRResponse } from 'swr'; +import { AxiosError } from 'axios'; + +export interface UpdateUserValues { + externalId: string; + username: string; + email: string; + password: string; + adminRoleId: number | null; + rootAdmin: boolean; +} + +const filters = ['id', 'uuid', 'external_id', 'username', 'email'] as const; +type Filters = typeof filters[number]; + +const useGetUsers = ( + params?: QueryBuilderParams, + config?: SWRConfiguration, +): SWRResponse, AxiosError> => { + return useSWR>( + ['/api/application/users', JSON.stringify(params)], + async () => { + const { data } = await http.get('/api/application/users', { + params: withQueryBuilderParams(params), + }); + + return getPaginationSet(data, Transformers.toUser); + }, + config || { revalidateOnMount: true, revalidateOnFocus: false }, + ); +}; + +const getUser = (id: number, include: string[] = []): Promise => { + return new Promise((resolve, reject) => { + http.get(`/api/application/users/${id}`, { params: { include: include.join(',') } }) + .then(({ data }) => resolve(Transformers.toUser(data))) + .catch(reject); + }); +}; + +const searchUserAccounts = async (params: QueryBuilderParams<'username' | 'email'>): Promise => { + const { data } = await http.get('/api/application/users', { + params: withQueryBuilderParams(params), + }); + + return data.data.map(Transformers.toUser); +}; + +const createUser = (values: UpdateUserValues, include: string[] = []): Promise => { + const data = {}; + Object.keys(values).forEach(k => { + // @ts-ignore + data[k.replace(/[A-Z]/g, l => `_${l.toLowerCase()}`)] = values[k]; + }); + + return new Promise((resolve, reject) => { + http.post('/api/application/users', data, { params: { include: include.join(',') } }) + .then(({ data }) => resolve(Transformers.toUser(data))) + .catch(reject); + }); +}; + +const updateUser = (id: number, values: Partial, include: string[] = []): Promise => { + const data = {}; + Object.keys(values).forEach(k => { + // Don't set password if it is empty. + if (k === 'password' && values[k] === '') { + return; + } + // @ts-ignore + data[k.replace(/[A-Z]/g, l => `_${l.toLowerCase()}`)] = values[k]; + }); + return new Promise((resolve, reject) => { + http.patch(`/api/application/users/${id}`, data, { params: { include: include.join(',') } }) + .then(({ data }) => resolve(Transformers.toUser(data))) + .catch(reject); + }); +}; + +const deleteUser = (id: number): Promise => { + return new Promise((resolve, reject) => { + http.delete(`/api/application/users/${id}`) + .then(() => resolve()) + .catch(reject); + }); +}; + +export { useGetUsers, getUser, searchUserAccounts, createUser, updateUser, deleteUser }; diff --git a/resources/scripts/api/definitions/admin/index.ts b/resources/scripts/api/definitions/admin/index.ts new file mode 100644 index 0000000000..39ac1f45ae --- /dev/null +++ b/resources/scripts/api/definitions/admin/index.ts @@ -0,0 +1,2 @@ +export * from './models.d'; +export { default as Transformers } from './transformers'; diff --git a/resources/scripts/api/definitions/admin/models.d.ts b/resources/scripts/api/definitions/admin/models.d.ts new file mode 100644 index 0000000000..627a448f97 --- /dev/null +++ b/resources/scripts/api/definitions/admin/models.d.ts @@ -0,0 +1,29 @@ +import { ModelWithRelationships, UUID } from '@/api/definitions'; +import { Server } from '@/api/admin/server'; + +interface User extends ModelWithRelationships { + id: number; + uuid: UUID; + externalId: string; + username: string; + email: string; + language: string; + adminRoleId: number | null; + roleName: string; + isRootAdmin: boolean; + isUsingTwoFactor: boolean; + avatarUrl: string; + createdAt: Date; + updatedAt: Date; + relationships: { + role: UserRole | null; + // TODO: just use an API call, this is probably a bad idea for performance. + servers?: Server[]; + }; +} + +interface UserRole extends ModelWithRelationships { + id: number; + name: string; + description: string; +} diff --git a/resources/scripts/api/definitions/admin/transformers.ts b/resources/scripts/api/definitions/admin/transformers.ts new file mode 100644 index 0000000000..07095ab07f --- /dev/null +++ b/resources/scripts/api/definitions/admin/transformers.ts @@ -0,0 +1,212 @@ +/* eslint-disable camelcase */ +import { Allocation, Node } from '@/api/admin/node'; +import { Server, ServerVariable } from '@/api/admin/server'; +import { FractalResponseData, FractalResponseList } from '@/api/http'; +import * as Models from '@definitions/admin/models'; +import { Location } from '@/api/admin/location'; +import { Egg, EggVariable } from '@/api/admin/egg'; +import { Nest } from '@/api/admin/nest'; + +const isList = (data: FractalResponseList | FractalResponseData): data is FractalResponseList => data.object === 'list'; + +function transform (data: undefined, transformer: (callback: FractalResponseData) => T, missing?: M): undefined; +function transform (data: FractalResponseData | undefined, transformer: (callback: FractalResponseData) => T, missing?: M): T | M | undefined; +function transform (data: FractalResponseList | undefined, transformer: (callback: FractalResponseData) => T, missing?: M): T[] | undefined; +function transform (data: FractalResponseData | FractalResponseList | undefined, transformer: (callback: FractalResponseData) => T, missing = undefined) { + if (data === undefined) return undefined; + + if (isList(data)) { + return data.data.map(transformer); + } + + return !data ? missing : transformer(data); +} + +export default class Transformers { + static toServer = ({ attributes }: FractalResponseData): Server => { + const { oom_disabled, ...limits } = attributes.limits; + const { allocations, egg, nest, node, user, variables } = attributes.relationships || {}; + + return { + id: attributes.id, + uuid: attributes.uuid, + externalId: attributes.external_id, + identifier: attributes.identifier, + name: attributes.name, + description: attributes.description, + status: attributes.status, + userId: attributes.owner_id, + nodeId: attributes.node_id, + allocationId: attributes.allocation_id, + eggId: attributes.egg_id, + nestId: attributes.nest_id, + limits: { ...limits, oomDisabled: oom_disabled }, + featureLimits: attributes.feature_limits, + container: attributes.container, + createdAt: new Date(attributes.created_at), + updatedAt: new Date(attributes.updated_at), + relationships: { + allocations: transform(allocations as FractalResponseList | undefined, this.toAllocation), + nest: transform(nest as FractalResponseData | undefined, this.toNest), + egg: transform(egg as FractalResponseData | undefined, this.toEgg), + node: transform(node as FractalResponseData | undefined, this.toNode), + user: transform(user as FractalResponseData | undefined, this.toUser), + variables: transform(variables as FractalResponseList | undefined, this.toServerEggVariable), + }, + }; + }; + + static toNode = ({ attributes }: FractalResponseData): Node => { + return { + id: attributes.id, + uuid: attributes.uuid, + isPublic: attributes.public, + locationId: attributes.location_id, + databaseHostId: attributes.database_host_id, + name: attributes.name, + description: attributes.description, + fqdn: attributes.fqdn, + ports: { + http: { + public: attributes.publicPortHttp, + listen: attributes.listenPortHttp, + }, + sftp: { + public: attributes.publicPortSftp, + listen: attributes.listenPortSftp, + }, + }, + scheme: attributes.scheme, + isBehindProxy: attributes.behindProxy, + isMaintenanceMode: attributes.maintenance_mode, + memory: attributes.memory, + memoryOverallocate: attributes.memory_overallocate, + disk: attributes.disk, + diskOverallocate: attributes.disk_overallocate, + uploadSize: attributes.upload_size, + daemonBase: attributes.daemonBase, + createdAt: new Date(attributes.created_at), + updatedAt: new Date(attributes.updated_at), + relationships: { + location: transform(attributes.relationships?.location as FractalResponseData, this.toLocation), + }, + }; + }; + + static toUserRole = ({ attributes }: FractalResponseData): Models.UserRole => ({ + id: attributes.id, + name: attributes.name, + description: attributes.description, + relationships: {}, + }); + + static toUser = ({ attributes }: FractalResponseData): Models.User => { + return { + id: attributes.id, + uuid: attributes.uuid, + externalId: attributes.external_id, + username: attributes.username, + email: attributes.email, + language: attributes.language, + adminRoleId: attributes.adminRoleId || null, + roleName: attributes.role_name, + isRootAdmin: attributes.root_admin, + isUsingTwoFactor: attributes['2fa'] || false, + avatarUrl: attributes.avatar_url, + createdAt: new Date(attributes.created_at), + updatedAt: new Date(attributes.updated_at), + relationships: { + role: transform(attributes.relationships?.role as FractalResponseData, this.toUserRole) || null, + }, + }; + }; + + static toLocation = ({ attributes }: FractalResponseData): Location => ({ + id: attributes.id, + short: attributes.short, + long: attributes.long, + createdAt: new Date(attributes.created_at), + updatedAt: new Date(attributes.updated_at), + relationships: { + nodes: transform(attributes.relationships?.node as FractalResponseList, this.toNode), + }, + }); + + static toEgg = ({ attributes }: FractalResponseData): Egg => ({ + id: attributes.id, + uuid: attributes.uuid, + nestId: attributes.nest_id, + author: attributes.author, + name: attributes.name, + description: attributes.description, + features: attributes.features, + dockerImages: attributes.docker_images, + configFiles: attributes.config?.files, + configStartup: attributes.config?.startup, + configStop: attributes.config?.stop, + configFrom: attributes.config?.extends, + startup: attributes.startup, + copyScriptFrom: attributes.copy_script_from, + scriptContainer: attributes.script?.container, + scriptEntry: attributes.script?.entry, + scriptIsPrivileged: attributes.script?.privileged, + scriptInstall: attributes.script?.install, + createdAt: new Date(attributes.created_at), + updatedAt: new Date(attributes.updated_at), + relationships: { + nest: transform(attributes.relationships?.nest as FractalResponseData, this.toNest), + variables: transform(attributes.relationships?.variables as FractalResponseList, this.toEggVariable), + }, + }); + + static toEggVariable = ({ attributes }: FractalResponseData): EggVariable => ({ + id: attributes.id, + eggId: attributes.egg_id, + name: attributes.name, + description: attributes.description, + environmentVariable: attributes.env_variable, + defaultValue: attributes.default_value, + isUserViewable: attributes.user_viewable, + isUserEditable: attributes.user_editable, + // isRequired: attributes.required, + rules: attributes.rules, + createdAt: new Date(attributes.created_at), + updatedAt: new Date(attributes.updated_at), + relationships: {}, + }); + + static toServerEggVariable = (data: FractalResponseData): ServerVariable => ({ + ...this.toEggVariable(data), + serverValue: data.attributes.server_value, + }); + + static toAllocation = ({ attributes }: FractalResponseData): Allocation => ({ + id: attributes.id, + ip: attributes.ip, + port: attributes.port, + alias: attributes.alias || null, + isAssigned: attributes.assigned, + relationships: { + node: transform(attributes.relationships?.node as FractalResponseData, this.toNode), + server: transform(attributes.relationships?.server as FractalResponseData, this.toServer), + }, + getDisplayText (): string { + const raw = `${this.ip}:${this.port}`; + + return !this.alias ? raw : `${this.alias} (${raw})`; + }, + }); + + static toNest = ({ attributes }: FractalResponseData): Nest => ({ + id: attributes.id, + uuid: attributes.uuid, + author: attributes.author, + name: attributes.name, + description: attributes.description, + createdAt: new Date(attributes.created_at), + updatedAt: new Date(attributes.updated_at), + relationships: { + eggs: transform(attributes.relationships?.eggs as FractalResponseList, this.toEgg), + }, + }); +} diff --git a/resources/scripts/components/App.tsx b/resources/scripts/components/App.tsx index c72ce426f5..ff53fb80e0 100644 --- a/resources/scripts/components/App.tsx +++ b/resources/scripts/components/App.tsx @@ -11,10 +11,12 @@ import Spinner from '@/components/elements/Spinner'; import { store } from '@/state'; import { ServerContext } from '@/state/server'; import { SiteSettings } from '@/state/settings'; +import { AdminContext } from '@/state/admin'; +const AdminRouter = lazy(() => import('@/routers/AdminRouter')); +const AuthenticationRouter = lazy(() => import('@/routers/AuthenticationRouter')); const DashboardRouter = lazy(() => import('@/routers/DashboardRouter')); const ServerRouter = lazy(() => import('@/routers/ServerRouter')); -const AuthenticationRouter = lazy(() => import('@/routers/AuthenticationRouter')); interface ExtendedWindow extends Window { SiteConfiguration?: SiteSettings; @@ -86,6 +88,17 @@ function App() { } /> + + + + + + } + /> + ( +
    + +
    + {typeof title === 'string' ? ( +

    + {icon && } + {title} +

    + ) : ( + title + )} + {button} +
    +
    {children}
    +
    +); + +export default AdminBox; diff --git a/resources/scripts/components/admin/AdminCheckbox.tsx b/resources/scripts/components/admin/AdminCheckbox.tsx new file mode 100644 index 0000000000..32ba2b00e3 --- /dev/null +++ b/resources/scripts/components/admin/AdminCheckbox.tsx @@ -0,0 +1,36 @@ +import type { ChangeEvent } from 'react'; +import tw, { styled } from 'twin.macro'; + +import Input from '@/components/elements/Input'; + +export const TableCheckbox = styled(Input)` + && { + ${tw`border-neutral-500 bg-transparent`}; + + &:not(:checked) { + ${tw`hover:border-neutral-300`}; + } + } +`; + +export default ({ + name, + checked, + onChange, +}: { + name: string; + checked: boolean; + onChange(e: ChangeEvent): void; +}) => { + return ( +
    + +
    + ); +}; diff --git a/resources/scripts/components/admin/AdminContentBlock.tsx b/resources/scripts/components/admin/AdminContentBlock.tsx new file mode 100644 index 0000000000..e182026e86 --- /dev/null +++ b/resources/scripts/components/admin/AdminContentBlock.tsx @@ -0,0 +1,42 @@ +import type { ReactNode } from 'react'; +import { useEffect } from 'react'; +// import { CSSTransition } from 'react-transition-group'; +import tw from 'twin.macro'; +import FlashMessageRender from '@/components/FlashMessageRender'; + +const AdminContentBlock: React.FC<{ + children: ReactNode; + title?: string; + showFlashKey?: string; + className?: string; +}> = ({ children, title, showFlashKey }) => { + useEffect(() => { + if (!title) { + return; + } + + document.title = `Admin | ${title}`; + }, [title]); + + return ( + // + <> + {showFlashKey && } + {children} + {/*

    + © 2015 - 2021  + + Pterodactyl Software + +

    */} + + //
    + ); +}; + +export default AdminContentBlock; diff --git a/resources/scripts/components/admin/AdminTable.tsx b/resources/scripts/components/admin/AdminTable.tsx new file mode 100644 index 0000000000..60c544a0a7 --- /dev/null +++ b/resources/scripts/components/admin/AdminTable.tsx @@ -0,0 +1,348 @@ +import { debounce } from 'debounce'; +import type { ChangeEvent, MouseEvent, ReactNode } from 'react'; +import { useCallback, useState } from 'react'; +import tw, { styled } from 'twin.macro'; + +import type { ListContext as TableHooks } from '@/api/admin'; +import type { PaginatedResult, PaginationDataSet } from '@/api/http'; +import { TableCheckbox } from '@/components/admin/AdminCheckbox'; +import Input from '@/components/elements/Input'; +import InputSpinner from '@/components/elements/InputSpinner'; +import Spinner from '@/components/elements/Spinner'; + +export function useTableHooks(initialState?: T | (() => T)): TableHooks { + const [page, setPage] = useState(1); + const [filters, setFilters] = useState(initialState || null); + const [sort, setSortState] = useState(null); + const [sortDirection, setSortDirection] = useState(false); + + const setSort = (newSort: string | null) => { + if (sort === newSort) { + setSortDirection(!sortDirection); + } else { + setSortState(newSort); + setSortDirection(false); + } + }; + + return { page, setPage, filters, setFilters, sort, setSort, sortDirection, setSortDirection }; +} + +export const TableHeader = ({ + name, + onClick, + direction, +}: { + name?: string; + onClick?: (e: MouseEvent) => void; + direction?: number | null; +}) => { + if (!name) { + return ; + } + + return ( + + + + {name} + + + {direction !== undefined ? ( +
    + + {direction === null || direction === 1 ? ( + + ) : null} + {direction === null || direction === 2 ? ( + + ) : null} + +
    + ) : null} +
    + + ); +}; + +export const TableHead = ({ children }: { children: ReactNode }) => { + return ( + + + + {children} + + + ); +}; + +export const TableBody = ({ children }: { children: ReactNode }) => { + return {children}; +}; + +export const TableRow = ({ children }: { children: ReactNode }) => { + return {children}; +}; + +interface Props { + data?: PaginatedResult; + onPageSelect: (page: number) => void; + + children: ReactNode; +} + +const PaginationButton = styled.button<{ active?: boolean }>` + ${tw`relative items-center px-3 py-1 -ml-px text-sm font-normal leading-5 transition duration-150 ease-in-out border border-neutral-500 focus:z-10 focus:outline-none focus:border-primary-300 inline-flex`}; + + ${props => + props.active ? tw`bg-neutral-500 text-neutral-50` : tw`bg-neutral-600 text-neutral-200 hover:text-neutral-50`}; +`; + +const PaginationArrow = styled.button` + ${tw`relative inline-flex items-center px-1 py-1 text-sm font-medium leading-5 transition duration-150 ease-in-out border border-neutral-500 bg-neutral-600 text-neutral-400 hover:text-neutral-50 focus:z-10 focus:outline-none focus:border-primary-300`}; + + &:disabled { + ${tw`bg-neutral-700`} + } + + &:hover:disabled { + ${tw`text-neutral-400 cursor-default`}; + } +`; + +export function Pagination({ data, onPageSelect, children }: Props) { + let pagination: PaginationDataSet; + if (data === undefined) { + pagination = { + total: 0, + count: 0, + perPage: 0, + currentPage: 1, + totalPages: 1, + }; + } else { + pagination = data.pagination; + } + + const setPage = (page: number) => { + if (page < 1 || page > pagination.totalPages) { + return; + } + + onPageSelect(page); + }; + + const isFirstPage = pagination.currentPage === 1; + const isLastPage = pagination.currentPage >= pagination.totalPages; + + const pages = []; + + if (pagination.totalPages < 7) { + for (let i = 1; i <= pagination.totalPages; i++) { + pages.push(i); + } + } else { + // Don't ask me how this works, all I know is that this code will always have 7 items in the pagination, + // and keeps the current page centered if it is not too close to the start or end. + let start = Math.max(pagination.currentPage - 3, 1); + const end = Math.min( + pagination.totalPages, + pagination.currentPage + (pagination.currentPage < 4 ? 7 - pagination.currentPage : 3), + ); + + while (start !== 1 && end - start !== 6) { + start--; + } + + for (let i = start; i <= end; i++) { + pages.push(i); + } + } + + return ( + <> + {children} + +
    +

    + Showing{' '} + + {(pagination.currentPage - 1) * pagination.perPage + (pagination.total > 0 ? 1 : 0)} + {' '} + to{' '} + + {(pagination.currentPage - 1) * pagination.perPage + pagination.count} + {' '} + of {pagination.total} results +

    + + {isFirstPage && isLastPage ? null : ( +
    + +
    + )} +
    + + ); +} + +export const Loading = () => { + return ( +
    + +
    + ); +}; + +export const NoItems = ({ className }: { className?: string }) => { + return ( +
    +
    + {'No +
    + +

    + No items could be found, it's almost like they are hiding. +

    +
    + ); +}; + +interface Params { + checked: boolean; + onSelectAllClick: (e: ChangeEvent) => void; + onSearch?: (query: string) => Promise; + + children: ReactNode; +} + +export const ContentWrapper = ({ checked, onSelectAllClick, onSearch, children }: Params) => { + const [loading, setLoading] = useState(false); + const [inputText, setInputText] = useState(''); + + const search = useCallback( + debounce((query: string) => { + if (onSearch === undefined) { + return; + } + + setLoading(true); + onSearch(query).then(() => setLoading(false)); + }, 200), + [], + ); + + return ( + <> +
    +
    + + + + + +
    + +
    + + { + setInputText(e.currentTarget.value); + search(e.currentTarget.value); + }} + /> + +
    +
    + + {children} + + ); +}; + +export default ({ children }: { children: ReactNode }) => { + return ( +
    +
    {children}
    +
    + ); +}; diff --git a/resources/scripts/components/admin/Sidebar.tsx b/resources/scripts/components/admin/Sidebar.tsx new file mode 100644 index 0000000000..7ff60cad24 --- /dev/null +++ b/resources/scripts/components/admin/Sidebar.tsx @@ -0,0 +1,87 @@ +import tw, { css, styled } from 'twin.macro'; + +import { withSubComponents } from '@/components/helpers'; + +const Wrapper = styled.div` + ${tw`w-full flex flex-col px-4`}; + + & > a { + ${tw`h-10 w-full flex flex-row items-center text-neutral-300 cursor-pointer select-none px-4`}; + ${tw`hover:text-neutral-50`}; + + & > svg { + ${tw`h-6 w-6 flex flex-shrink-0`}; + } + + & > span { + ${tw`font-header font-medium text-lg whitespace-nowrap leading-none ml-3`}; + } + + &:active, + &.active { + ${tw`text-neutral-50 bg-neutral-800 rounded`}; + } + } +`; + +const Section = styled.div` + ${tw`h-[18px] font-header font-medium text-xs text-neutral-300 whitespace-nowrap uppercase ml-4 mb-1 select-none`}; + + &:not(:first-of-type) { + ${tw`mt-4`}; + } +`; + +const User = styled.div` + ${tw`h-16 w-full flex items-center bg-neutral-700 justify-center`}; +`; + +const Sidebar = styled.div<{ $collapsed?: boolean }>` + ${tw`h-screen hidden md:flex flex-col items-center flex-shrink-0 bg-neutral-900 overflow-x-hidden ease-linear`}; + ${tw`transition-[width] duration-150 ease-in`}; + ${tw`w-[17.5rem]`}; + + & > a { + ${tw`h-10 w-full flex flex-row items-center text-neutral-300 cursor-pointer select-none px-8`}; + ${tw`hover:text-neutral-50`}; + + & > svg { + ${tw`transition-none h-6 w-6 flex flex-shrink-0`}; + } + + & > span { + ${tw`font-header font-medium text-lg whitespace-nowrap leading-none ml-3`}; + } + } + + ${props => + props.$collapsed && + css` + ${tw`w-20`}; + + ${Section} { + ${tw`invisible`}; + } + + ${Wrapper} { + ${tw`px-5`}; + + & > a { + ${tw`justify-center px-0`}; + } + } + + & > a { + ${tw`justify-center px-4`}; + } + + & > a > span, + ${User} > div, + ${User} > a, + ${Wrapper} > a > span { + ${tw`hidden`}; + } + `}; +`; + +export default withSubComponents(Sidebar, { Section, Wrapper, User }); diff --git a/resources/scripts/components/admin/SubNavigation.tsx b/resources/scripts/components/admin/SubNavigation.tsx new file mode 100644 index 0000000000..5af3f28f03 --- /dev/null +++ b/resources/scripts/components/admin/SubNavigation.tsx @@ -0,0 +1,42 @@ +import type { ComponentType, ReactNode } from 'react'; +import { NavLink } from 'react-router-dom'; +import tw, { styled } from 'twin.macro'; + +export const SubNavigation = styled.div` + ${tw`flex flex-row items-center flex-shrink-0 h-12 mb-4 border-b border-neutral-700`}; + + & > a { + ${tw`flex flex-row items-center h-full px-4 border-b text-neutral-300 text-base whitespace-nowrap border-transparent`}; + + & > svg { + ${tw`w-6 h-6 mr-2`}; + } + + &:active, + &.active { + ${tw`text-primary-300 border-primary-300`}; + } + } +`; + +interface Props { + to: string; + name: string; +} + +interface PropsWithIcon extends Props { + icon: ComponentType; + children?: never; +} + +interface PropsWithoutIcon extends Props { + icon?: never; + children: ReactNode; +} + +export const SubNavigationLink = ({ to, name, icon: IconComponent, children }: PropsWithIcon | PropsWithoutIcon) => ( + + {IconComponent ? : children} + {name} + +); diff --git a/resources/scripts/components/admin/databases/DatabaseDeleteButton.tsx b/resources/scripts/components/admin/databases/DatabaseDeleteButton.tsx new file mode 100644 index 0000000000..be08b56fe9 --- /dev/null +++ b/resources/scripts/components/admin/databases/DatabaseDeleteButton.tsx @@ -0,0 +1,73 @@ +import { Actions, useStoreActions } from 'easy-peasy'; +import { useState } from 'react'; +import tw from 'twin.macro'; + +import deleteDatabase from '@/api/admin/databases/deleteDatabase'; +import Button from '@/components/elements/Button'; +import ConfirmationModal from '@/components/elements/ConfirmationModal'; +import type { ApplicationStore } from '@/state'; + +interface Props { + databaseId: number; + onDeleted: () => void; +} + +export default ({ databaseId, onDeleted }: Props) => { + const [visible, setVisible] = useState(false); + const [loading, setLoading] = useState(false); + + const { clearFlashes, clearAndAddHttpError } = useStoreActions( + (actions: Actions) => actions.flashes, + ); + + const onDelete = () => { + setLoading(true); + clearFlashes('database'); + + deleteDatabase(databaseId) + .then(() => { + setLoading(false); + onDeleted(); + }) + .catch(error => { + console.error(error); + clearAndAddHttpError({ key: 'database', error }); + + setLoading(false); + setVisible(false); + }); + }; + + return ( + <> + setVisible(false)} + > + Are you sure you want to delete this database host? This action will delete all knowledge of databases + created on this host but not the databases themselves. + + + + + ); +}; diff --git a/resources/scripts/components/admin/databases/DatabaseEditContainer.tsx b/resources/scripts/components/admin/databases/DatabaseEditContainer.tsx new file mode 100644 index 0000000000..89d8df46fd --- /dev/null +++ b/resources/scripts/components/admin/databases/DatabaseEditContainer.tsx @@ -0,0 +1,235 @@ +import type { Action, Actions } from 'easy-peasy'; +import { action, createContextStore, useStoreActions } from 'easy-peasy'; +import type { FormikHelpers } from 'formik'; +import { Form, Formik } from 'formik'; +import { useEffect, useState } from 'react'; +import { useNavigate, useParams } from 'react-router-dom'; +import tw from 'twin.macro'; +import { number, object, string } from 'yup'; + +import type { Database } from '@/api/admin/databases/getDatabases'; +import getDatabase from '@/api/admin/databases/getDatabase'; +import updateDatabase from '@/api/admin/databases/updateDatabase'; +import AdminContentBlock from '@/components/admin/AdminContentBlock'; +import Spinner from '@/components/elements/Spinner'; +import FlashMessageRender from '@/components/FlashMessageRender'; +import AdminBox from '@/components/admin/AdminBox'; +import Button from '@/components/elements/Button'; +import Field from '@/components/elements/Field'; +import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; +import DatabaseDeleteButton from '@/components/admin/databases/DatabaseDeleteButton'; +import type { ApplicationStore } from '@/state'; + +interface ctx { + database: Database | undefined; + setDatabase: Action; +} + +export const Context = createContextStore({ + database: undefined, + + setDatabase: action((state, payload) => { + state.database = payload; + }), +}); + +export interface Values { + name: string; + host: string; + port: number; + username: string; + password: string; +} + +export interface Params { + title: string; + initialValues?: Values; + children?: React.ReactNode; + + onSubmit: (values: Values, helpers: FormikHelpers) => void; +} + +export const InformationContainer = ({ title, initialValues, children, onSubmit }: Params) => { + const submit = (values: Values, helpers: FormikHelpers) => { + onSubmit(values, helpers); + }; + + if (!initialValues) { + initialValues = { + name: '', + host: '', + port: 3306, + username: '', + password: '', + }; + } + + return ( + + {({ isSubmitting, isValid }) => ( + <> + + + +
    +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + {children} +
    + +
    +
    +
    +
    + + )} +
    + ); +}; + +const EditInformationContainer = () => { + const navigate = useNavigate(); + + const { clearFlashes, clearAndAddHttpError } = useStoreActions( + (actions: Actions) => actions.flashes, + ); + + const database = Context.useStoreState(state => state.database); + const setDatabase = Context.useStoreActions(actions => actions.setDatabase); + + if (database === undefined) { + return <>; + } + + const submit = ({ name, host, port, username, password }: Values, { setSubmitting }: FormikHelpers) => { + clearFlashes('database'); + + updateDatabase(database.id, name, host, port, username, password || undefined) + .then(() => setDatabase({ ...database, name, host, port, username })) + .catch(error => { + console.error(error); + clearAndAddHttpError({ key: 'database', error }); + }) + .then(() => setSubmitting(false)); + }; + + return ( + +
    + navigate('/admin/databases')} /> +
    +
    + ); +}; + +const DatabaseEditContainer = () => { + const params = useParams<'id'>(); + + const { clearFlashes, clearAndAddHttpError } = useStoreActions( + (actions: Actions) => actions.flashes, + ); + const [loading, setLoading] = useState(true); + + const database = Context.useStoreState(state => state.database); + const setDatabase = Context.useStoreActions(actions => actions.setDatabase); + + useEffect(() => { + clearFlashes('database'); + + getDatabase(Number(params.id)) + .then(database => setDatabase(database)) + .catch(error => { + console.error(error); + clearAndAddHttpError({ key: 'database', error }); + }) + .then(() => setLoading(false)); + }, []); + + if (loading || database === undefined) { + return ( + + + +
    + +
    +
    + ); + } + + return ( + +
    +
    +

    {database.name}

    +

    + {database.getAddress()} +

    +
    +
    + + + + +
    + ); +}; + +export default () => { + return ( + + + + ); +}; diff --git a/resources/scripts/components/admin/databases/DatabasesContainer.tsx b/resources/scripts/components/admin/databases/DatabasesContainer.tsx new file mode 100644 index 0000000000..73ff3027c0 --- /dev/null +++ b/resources/scripts/components/admin/databases/DatabasesContainer.tsx @@ -0,0 +1,194 @@ +import { useContext, useEffect } from 'react'; +import { NavLink } from 'react-router-dom'; +import tw from 'twin.macro'; + +import type { Filters } from '@/api/admin/databases/getDatabases'; +import getDatabases, { Context as DatabasesContext } from '@/api/admin/databases/getDatabases'; +import useFlash from '@/plugins/useFlash'; +import { AdminContext } from '@/state/admin'; +import FlashMessageRender from '@/components/FlashMessageRender'; +import AdminCheckbox from '@/components/admin/AdminCheckbox'; +import AdminContentBlock from '@/components/admin/AdminContentBlock'; +import AdminTable, { + TableBody, + TableHead, + TableHeader, + TableRow, + Pagination, + Loading, + NoItems, + ContentWrapper, + useTableHooks, +} from '@/components/admin/AdminTable'; +import Button from '@/components/elements/Button'; +import CopyOnClick from '@/components/elements/CopyOnClick'; + +const RowCheckbox = ({ id }: { id: number }) => { + const isChecked = AdminContext.useStoreState(state => state.databases.selectedDatabases.indexOf(id) >= 0); + const appendSelectedDatabase = AdminContext.useStoreActions(actions => actions.databases.appendSelectedDatabase); + const removeSelectedDatabase = AdminContext.useStoreActions(actions => actions.databases.removeSelectedDatabase); + + return ( + ) => { + if (e.currentTarget.checked) { + appendSelectedDatabase(id); + } else { + removeSelectedDatabase(id); + } + }} + /> + ); +}; + +const DatabasesContainer = () => { + const { page, setPage, setFilters, sort, setSort, sortDirection } = useContext(DatabasesContext); + const { clearFlashes, clearAndAddHttpError } = useFlash(); + const { data: databases, error, isValidating } = getDatabases(); + + useEffect(() => { + if (!error) { + clearFlashes('databases'); + return; + } + + clearAndAddHttpError({ key: 'databases', error }); + }, [error]); + + const length = databases?.items?.length || 0; + + const setSelectedDatabases = AdminContext.useStoreActions(actions => actions.databases.setSelectedDatabases); + const selectedDatabasesLength = AdminContext.useStoreState(state => state.databases.selectedDatabases.length); + + const onSelectAllClick = (e: React.ChangeEvent) => { + setSelectedDatabases(e.currentTarget.checked ? databases?.items?.map(database => database.id) || [] : []); + }; + + const onSearch = (query: string): Promise => { + return new Promise(resolve => { + if (query.length < 2) { + setFilters(null); + } else { + setFilters({ name: query }); + } + return resolve(); + }); + }; + + useEffect(() => { + setSelectedDatabases([]); + }, [page]); + + return ( + +
    +
    +

    Database Hosts

    +

    + Database hosts that servers can have databases created on. +

    +
    + +
    + + + +
    +
    + + + + + + +
    + + + setSort('id')} + /> + setSort('name')} + /> + + + + + + {databases !== undefined && + !error && + !isValidating && + length > 0 && + databases.items.map(database => ( + + + + + + + + + + + + ))} + +
    + + + + + {database.id} + + + + + {database.name} + + + + + {database.getAddress()} + + + + {database.username} +
    + + {databases === undefined || (error && isValidating) ? ( + + ) : length < 1 ? ( + + ) : null} +
    +
    +
    +
    +
    + ); +}; + +export default () => { + const hooks = useTableHooks(); + + return ( + + + + ); +}; diff --git a/resources/scripts/components/admin/databases/NewDatabaseContainer.tsx b/resources/scripts/components/admin/databases/NewDatabaseContainer.tsx new file mode 100644 index 0000000000..3bc0afa94f --- /dev/null +++ b/resources/scripts/components/admin/databases/NewDatabaseContainer.tsx @@ -0,0 +1,48 @@ +import type { Actions } from 'easy-peasy'; +import { useStoreActions } from 'easy-peasy'; +import type { FormikHelpers } from 'formik'; +import { useNavigate } from 'react-router-dom'; +import tw from 'twin.macro'; + +import createDatabase from '@/api/admin/databases/createDatabase'; +import AdminContentBlock from '@/components/admin/AdminContentBlock'; +import { InformationContainer, Values } from '@/components/admin/databases/DatabaseEditContainer'; +import FlashMessageRender from '@/components/FlashMessageRender'; +import { ApplicationStore } from '@/state'; + +export default () => { + const navigate = useNavigate(); + + const { clearFlashes, clearAndAddHttpError } = useStoreActions( + (actions: Actions) => actions.flashes, + ); + + const submit = ({ name, host, port, username, password }: Values, { setSubmitting }: FormikHelpers) => { + clearFlashes('database:create'); + + createDatabase(name, host, port, username, password) + .then(database => navigate(`/admin/databases/${database.id}`)) + .catch(error => { + console.error(error); + clearAndAddHttpError({ key: 'database:create', error }); + }) + .then(() => setSubmitting(false)); + }; + + return ( + +
    +
    +

    New Database Host

    +

    + Add a new database host to the panel. +

    +
    +
    + + + + +
    + ); +}; diff --git a/resources/scripts/components/admin/locations/LocationDeleteButton.tsx b/resources/scripts/components/admin/locations/LocationDeleteButton.tsx new file mode 100644 index 0000000000..9fb80954e2 --- /dev/null +++ b/resources/scripts/components/admin/locations/LocationDeleteButton.tsx @@ -0,0 +1,74 @@ +import type { Actions } from 'easy-peasy'; +import { useStoreActions } from 'easy-peasy'; +import { useState } from 'react'; +import tw from 'twin.macro'; + +import deleteLocation from '@/api/admin/locations/deleteLocation'; +import Button from '@/components/elements/Button'; +import ConfirmationModal from '@/components/elements/ConfirmationModal'; +import type { ApplicationStore } from '@/state'; + +interface Props { + locationId: number; + onDeleted: () => void; +} + +export default ({ locationId, onDeleted }: Props) => { + const [visible, setVisible] = useState(false); + const [loading, setLoading] = useState(false); + + const { clearFlashes, clearAndAddHttpError } = useStoreActions( + (actions: Actions) => actions.flashes, + ); + + const onDelete = () => { + setLoading(true); + clearFlashes('location'); + + deleteLocation(locationId) + .then(() => { + setLoading(false); + onDeleted(); + }) + .catch(error => { + console.error(error); + clearAndAddHttpError({ key: 'location', error }); + + setLoading(false); + setVisible(false); + }); + }; + + return ( + <> + setVisible(false)} + > + Are you sure you want to delete this location? You may only delete a location if no nodes are assigned + to it. + + + + + ); +}; diff --git a/resources/scripts/components/admin/locations/LocationEditContainer.tsx b/resources/scripts/components/admin/locations/LocationEditContainer.tsx new file mode 100644 index 0000000000..6c0e74c6fc --- /dev/null +++ b/resources/scripts/components/admin/locations/LocationEditContainer.tsx @@ -0,0 +1,180 @@ +import type { Action, Actions } from 'easy-peasy'; +import { action, createContextStore, useStoreActions } from 'easy-peasy'; +import type { FormikHelpers } from 'formik'; +import { Form, Formik } from 'formik'; +import { useEffect, useState } from 'react'; +import { useNavigate, useParams } from 'react-router-dom'; +import tw from 'twin.macro'; +import { object, string } from 'yup'; + +import type { Location } from '@/api/admin/locations/getLocations'; +import getLocation from '@/api/admin/locations/getLocation'; +import updateLocation from '@/api/admin/locations/updateLocation'; +import AdminBox from '@/components/admin/AdminBox'; +import AdminContentBlock from '@/components/admin/AdminContentBlock'; +import LocationDeleteButton from '@/components/admin/locations/LocationDeleteButton'; +import Button from '@/components/elements/Button'; +import Field from '@/components/elements/Field'; +import Spinner from '@/components/elements/Spinner'; +import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; +import FlashMessageRender from '@/components/FlashMessageRender'; +import type { ApplicationStore } from '@/state'; + +interface ctx { + location: Location | undefined; + setLocation: Action; +} + +export const Context = createContextStore({ + location: undefined, + + setLocation: action((state, payload) => { + state.location = payload; + }), +}); + +interface Values { + short: string; + long: string; +} + +const EditInformationContainer = () => { + const navigate = useNavigate(); + + const { clearFlashes, clearAndAddHttpError } = useStoreActions( + (actions: Actions) => actions.flashes, + ); + + const location = Context.useStoreState(state => state.location); + const setLocation = Context.useStoreActions(actions => actions.setLocation); + + if (location === undefined) { + return <>; + } + + const submit = ({ short, long }: Values, { setSubmitting }: FormikHelpers) => { + clearFlashes('location'); + + updateLocation(location.id, short, long) + .then(() => setLocation({ ...location, short, long })) + .catch(error => { + console.error(error); + clearAndAddHttpError({ key: 'location', error }); + }) + .then(() => setSubmitting(false)); + }; + + return ( + + {({ isSubmitting, isValid }) => ( + <> + + + +
    +
    + +
    + +
    + +
    + +
    +
    + navigate('/admin/locations')} + /> +
    + +
    + +
    +
    +
    +
    + + )} +
    + ); +}; + +const LocationEditContainer = () => { + const params = useParams<'id'>(); + + const { clearFlashes, clearAndAddHttpError } = useStoreActions( + (actions: Actions) => actions.flashes, + ); + const [loading, setLoading] = useState(true); + + const location = Context.useStoreState(state => state.location); + const setLocation = Context.useStoreActions(actions => actions.setLocation); + + useEffect(() => { + clearFlashes('location'); + + getLocation(Number(params.id)) + .then(location => setLocation(location)) + .catch(error => { + console.error(error); + clearAndAddHttpError({ key: 'location', error }); + }) + .then(() => setLoading(false)); + }, []); + + if (loading || location === undefined) { + return ( + + + +
    + +
    +
    + ); + } + + return ( + +
    +
    +

    {location.short}

    + {(location.long || '').length < 1 ? ( +

    + No long name +

    + ) : ( +

    + {location.long} +

    + )} +
    +
    + + + + +
    + ); +}; + +export default () => { + return ( + + + + ); +}; diff --git a/resources/scripts/components/admin/locations/LocationsContainer.tsx b/resources/scripts/components/admin/locations/LocationsContainer.tsx new file mode 100644 index 0000000000..81ddd710a2 --- /dev/null +++ b/resources/scripts/components/admin/locations/LocationsContainer.tsx @@ -0,0 +1,186 @@ +import type { ChangeEvent } from 'react'; +import { useContext, useEffect } from 'react'; +import { NavLink } from 'react-router-dom'; +import tw from 'twin.macro'; + +import type { Filters } from '@/api/admin/locations/getLocations'; +import getLocations, { Context as LocationsContext } from '@/api/admin/locations/getLocations'; +import AdminContentBlock from '@/components/admin/AdminContentBlock'; +import AdminCheckbox from '@/components/admin/AdminCheckbox'; +import AdminTable, { + TableBody, + TableHead, + TableHeader, + TableRow, + Pagination, + Loading, + NoItems, + ContentWrapper, + useTableHooks, +} from '@/components/admin/AdminTable'; +import NewLocationButton from '@/components/admin/locations/NewLocationButton'; +import CopyOnClick from '@/components/elements/CopyOnClick'; +import FlashMessageRender from '@/components/FlashMessageRender'; +import useFlash from '@/plugins/useFlash'; +import { AdminContext } from '@/state/admin'; + +const RowCheckbox = ({ id }: { id: number }) => { + const isChecked = AdminContext.useStoreState(state => state.locations.selectedLocations.indexOf(id) >= 0); + const appendSelectedLocation = AdminContext.useStoreActions(actions => actions.locations.appendSelectedLocation); + const removeSelectedLocation = AdminContext.useStoreActions(actions => actions.locations.removeSelectedLocation); + + return ( + ) => { + if (e.currentTarget.checked) { + appendSelectedLocation(id); + } else { + removeSelectedLocation(id); + } + }} + /> + ); +}; + +const LocationsContainer = () => { + const { page, setPage, setFilters, sort, setSort, sortDirection } = useContext(LocationsContext); + const { clearFlashes, clearAndAddHttpError } = useFlash(); + const { data: locations, error, isValidating } = getLocations(); + + useEffect(() => { + if (!error) { + clearFlashes('locations'); + return; + } + + clearAndAddHttpError({ key: 'locations', error }); + }, [error]); + + const length = locations?.items?.length || 0; + + const setSelectedLocations = AdminContext.useStoreActions(actions => actions.locations.setSelectedLocations); + const selectedLocationsLength = AdminContext.useStoreState(state => state.locations.selectedLocations.length); + + const onSelectAllClick = (e: ChangeEvent) => { + setSelectedLocations(e.currentTarget.checked ? locations?.items?.map(location => location.id) || [] : []); + }; + + const onSearch = (query: string): Promise => { + return new Promise(resolve => { + if (query.length < 2) { + setFilters(null); + } else { + setFilters({ short: query }); + } + return resolve(); + }); + }; + + useEffect(() => { + setSelectedLocations([]); + }, [page]); + + return ( + +
    +
    +

    Locations

    +

    + All locations that nodes can be assigned to for easier categorization. +

    +
    + +
    + +
    +
    + + + + + + +
    + + + setSort('id')} + /> + setSort('short')} + /> + setSort('long')} + /> + + + + {locations !== undefined && + !error && + !isValidating && + length > 0 && + locations.items.map(location => ( + + + + + + + + + + ))} + +
    + + + + + {location.id} + + + + + {location.short} + + + {location.long} +
    + + {locations === undefined || (error && isValidating) ? ( + + ) : length < 1 ? ( + + ) : null} +
    +
    +
    +
    +
    + ); +}; + +export default () => { + const hooks = useTableHooks(); + + return ( + + + + ); +}; diff --git a/resources/scripts/components/admin/locations/NewLocationButton.tsx b/resources/scripts/components/admin/locations/NewLocationButton.tsx new file mode 100644 index 0000000000..f7f688e9b4 --- /dev/null +++ b/resources/scripts/components/admin/locations/NewLocationButton.tsx @@ -0,0 +1,112 @@ +import type { FormikHelpers } from 'formik'; +import { Form, Formik } from 'formik'; +import { useState } from 'react'; +import tw from 'twin.macro'; +import { object, string } from 'yup'; + +import createLocation from '@/api/admin/locations/createLocation'; +import getLocations from '@/api/admin/locations/getLocations'; +import Button from '@/components/elements/Button'; +import Field from '@/components/elements/Field'; +import Modal from '@/components/elements/Modal'; +import FlashMessageRender from '@/components/FlashMessageRender'; +import useFlash from '@/plugins/useFlash'; + +interface Values { + short: string; + long: string; +} + +const schema = object().shape({ + short: string() + .required('A location short name must be provided.') + .max(32, 'Location short name must not exceed 32 characters.'), + long: string().max(255, 'Location long name must not exceed 255 characters.'), +}); + +export default () => { + const [visible, setVisible] = useState(false); + const { clearFlashes, clearAndAddHttpError } = useFlash(); + const { mutate } = getLocations(); + + const submit = ({ short, long }: Values, { setSubmitting }: FormikHelpers) => { + clearFlashes('location:create'); + setSubmitting(true); + + createLocation(short, long) + .then(async location => { + await mutate(data => ({ ...data!, items: data!.items.concat(location) }), false); + setVisible(false); + }) + .catch(error => { + clearAndAddHttpError({ key: 'location:create', error }); + setSubmitting(false); + }); + }; + + return ( + <> + + {({ isSubmitting, resetForm }) => ( + { + resetForm(); + setVisible(false); + }} + > + + +

    New Location

    + +
    + + +
    + +
    + +
    + + +
    + +
    + )} +
    + + + + ); +}; diff --git a/resources/scripts/components/admin/mounts/MountDeleteButton.tsx b/resources/scripts/components/admin/mounts/MountDeleteButton.tsx new file mode 100644 index 0000000000..e515e50e28 --- /dev/null +++ b/resources/scripts/components/admin/mounts/MountDeleteButton.tsx @@ -0,0 +1,73 @@ +import type { Actions } from 'easy-peasy'; +import { useStoreActions } from 'easy-peasy'; +import { useState } from 'react'; +import tw from 'twin.macro'; + +import deleteMount from '@/api/admin/mounts/deleteMount'; +import Button from '@/components/elements/Button'; +import ConfirmationModal from '@/components/elements/ConfirmationModal'; +import type { ApplicationStore } from '@/state'; + +interface Props { + mountId: number; + onDeleted: () => void; +} + +export default ({ mountId, onDeleted }: Props) => { + const [visible, setVisible] = useState(false); + const [loading, setLoading] = useState(false); + + const { clearFlashes, clearAndAddHttpError } = useStoreActions( + (actions: Actions) => actions.flashes, + ); + + const onDelete = () => { + setLoading(true); + clearFlashes('mount'); + + deleteMount(mountId) + .then(() => { + setLoading(false); + onDeleted(); + }) + .catch(error => { + console.error(error); + clearAndAddHttpError({ key: 'mount', error }); + + setLoading(false); + setVisible(false); + }); + }; + + return ( + <> + setVisible(false)} + > + Are you sure you want to delete this mount? Deleting a mount will not delete files on any nodes. + + + + + ); +}; diff --git a/resources/scripts/components/admin/mounts/MountEditContainer.tsx b/resources/scripts/components/admin/mounts/MountEditContainer.tsx new file mode 100644 index 0000000000..a9282cf17a --- /dev/null +++ b/resources/scripts/components/admin/mounts/MountEditContainer.tsx @@ -0,0 +1,142 @@ +import type { Action, Actions } from 'easy-peasy'; +import { action, createContextStore, useStoreActions } from 'easy-peasy'; +import type { FormikHelpers } from 'formik'; +import { useEffect, useState } from 'react'; +import { useNavigate, useParams } from 'react-router-dom'; +import tw from 'twin.macro'; + +import type { Mount } from '@/api/admin/mounts/getMounts'; +import getMount from '@/api/admin/mounts/getMount'; +import updateMount from '@/api/admin/mounts/updateMount'; +import AdminContentBlock from '@/components/admin/AdminContentBlock'; +import MountDeleteButton from '@/components/admin/mounts/MountDeleteButton'; +import MountForm from '@/components/admin/mounts/MountForm'; +import Spinner from '@/components/elements/Spinner'; +import FlashMessageRender from '@/components/FlashMessageRender'; +import type { ApplicationStore } from '@/state'; + +interface ctx { + mount: Mount | undefined; + setMount: Action; +} + +export const Context = createContextStore({ + mount: undefined, + + setMount: action((state, payload) => { + state.mount = payload; + }), +}); + +const MountEditContainer = () => { + const navigate = useNavigate(); + const params = useParams<'id'>(); + + const { clearFlashes, clearAndAddHttpError } = useStoreActions( + (actions: Actions) => actions.flashes, + ); + const [loading, setLoading] = useState(true); + + const mount = Context.useStoreState(state => state.mount); + const setMount = Context.useStoreActions(actions => actions.setMount); + + const submit = ( + { name, description, source, target, readOnly, userMountable }: any, + { setSubmitting }: FormikHelpers, + ) => { + if (mount === undefined) { + return; + } + + clearFlashes('mount'); + + updateMount(mount.id, name, description, source, target, readOnly === '1', userMountable === '1') + .then(() => + setMount({ + ...mount, + name, + description, + source, + target, + readOnly: readOnly === '1', + userMountable: userMountable === '1', + }), + ) + .catch(error => { + console.error(error); + clearAndAddHttpError({ key: 'mount', error }); + }) + .then(() => setSubmitting(false)); + }; + + useEffect(() => { + clearFlashes('mount'); + + getMount(Number(params.id)) + .then(mount => setMount(mount)) + .catch(error => { + console.error(error); + clearAndAddHttpError({ key: 'mount', error }); + }) + .then(() => setLoading(false)); + }, []); + + if (loading || mount === undefined) { + return ( + + + +
    + +
    +
    + ); + } + + return ( + +
    +
    +

    {mount.name}

    + {(mount.description || '').length < 1 ? ( +

    + No description +

    + ) : ( +

    + {mount.description} +

    + )} +
    +
    + + + + +
    + navigate('/admin/mounts')} /> +
    +
    +
    + ); +}; + +export default () => { + return ( + + + + ); +}; diff --git a/resources/scripts/components/admin/mounts/MountForm.tsx b/resources/scripts/components/admin/mounts/MountForm.tsx new file mode 100644 index 0000000000..a86b947a8b --- /dev/null +++ b/resources/scripts/components/admin/mounts/MountForm.tsx @@ -0,0 +1,133 @@ +import type { FormikHelpers } from 'formik'; +import { Field as FormikField, Form, Formik } from 'formik'; +import tw from 'twin.macro'; +import { boolean, object, string } from 'yup'; + +import AdminBox from '@/components/admin/AdminBox'; +import Button from '@/components/elements/Button'; +import Field from '@/components/elements/Field'; +import Label from '@/components/elements/Label'; +import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; + +interface Values { + name: string; + description: string; + source: string; + target: string; + readOnly: string; + userMountable: string; +} + +interface Props { + action: string; + title: string; + initialValues?: Values; + + onSubmit: (values: Values, helpers: FormikHelpers) => void; + + children?: React.ReactNode; +} + +function MountForm({ action, title, initialValues, children, onSubmit }: Props) { + const submit = (values: Values, helpers: FormikHelpers) => { + onSubmit(values, helpers); + }; + + if (!initialValues) { + initialValues = { + name: '', + description: '', + source: '', + target: '', + readOnly: '0', + userMountable: '0', + }; + } + + return ( + + {({ isSubmitting, isValid }) => ( + + + +
    +
    + +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    +
    + + +
    + + + +
    +
    + +
    + + +
    + + + +
    +
    +
    + +
    + {children} + +
    + +
    +
    +
    +
    + )} +
    + ); +} + +export default MountForm; diff --git a/resources/scripts/components/admin/mounts/MountsContainer.tsx b/resources/scripts/components/admin/mounts/MountsContainer.tsx new file mode 100644 index 0000000000..bc3e0054da --- /dev/null +++ b/resources/scripts/components/admin/mounts/MountsContainer.tsx @@ -0,0 +1,241 @@ +import type { ChangeEvent } from 'react'; +import { useContext, useEffect } from 'react'; +import { NavLink } from 'react-router-dom'; +import tw from 'twin.macro'; + +import type { Filters } from '@/api/admin/mounts/getMounts'; +import getMounts, { Context as MountsContext } from '@/api/admin/mounts/getMounts'; +import AdminCheckbox from '@/components/admin/AdminCheckbox'; +import AdminContentBlock from '@/components/admin/AdminContentBlock'; +import AdminTable, { + TableBody, + TableHead, + TableHeader, + TableRow, + Pagination, + Loading, + NoItems, + ContentWrapper, + useTableHooks, +} from '@/components/admin/AdminTable'; +import Button from '@/components/elements/Button'; +import CopyOnClick from '@/components/elements/CopyOnClick'; +import FlashMessageRender from '@/components/FlashMessageRender'; +import useFlash from '@/plugins/useFlash'; +import { AdminContext } from '@/state/admin'; + +const RowCheckbox = ({ id }: { id: number }) => { + const isChecked = AdminContext.useStoreState(state => state.mounts.selectedMounts.indexOf(id) >= 0); + const appendSelectedMount = AdminContext.useStoreActions(actions => actions.mounts.appendSelectedMount); + const removeSelectedMount = AdminContext.useStoreActions(actions => actions.mounts.removeSelectedMount); + + return ( + ) => { + if (e.currentTarget.checked) { + appendSelectedMount(id); + } else { + removeSelectedMount(id); + } + }} + /> + ); +}; + +const MountsContainer = () => { + const { page, setPage, setFilters, sort, setSort, sortDirection } = useContext(MountsContext); + const { clearFlashes, clearAndAddHttpError } = useFlash(); + const { data: mounts, error, isValidating } = getMounts(); + + useEffect(() => { + if (!error) { + clearFlashes('mounts'); + return; + } + + clearAndAddHttpError({ key: 'mounts', error }); + }, [error]); + + const length = mounts?.items?.length || 0; + + const setSelectedMounts = AdminContext.useStoreActions(actions => actions.mounts.setSelectedMounts); + const selectedMountsLength = AdminContext.useStoreState(state => state.mounts.selectedMounts.length); + + const onSelectAllClick = (e: ChangeEvent) => { + setSelectedMounts(e.currentTarget.checked ? mounts?.items?.map(mount => mount.id) || [] : []); + }; + + const onSearch = (query: string): Promise => { + return new Promise(resolve => { + if (query.length < 2) { + setFilters(null); + } else { + setFilters({ name: query }); + } + return resolve(); + }); + }; + + useEffect(() => { + setSelectedMounts([]); + }, [page]); + + return ( + +
    +
    +

    Mounts

    +

    + Configure and manage additional mount points for servers. +

    +
    + +
    + + + +
    +
    + + + + + + +
    + + + setSort('id')} + /> + setSort('name')} + /> + setSort('source')} + /> + setSort('target')} + /> + + + + + + + + + + + + + + + ))} + +
    + + + + + {mounts !== undefined && + !error && + !isValidating && + length > 0 && + mounts.items.map(mount => ( + + + + + + + {mount.id} + + + + + {mount.name} + + + + + {mount.source} + + + + + + {mount.target} + + + + {mount.readOnly ? ( + + Read Only + + ) : ( + + Writable + + )} + + {mount.userMountable ? ( + + Mountable + + ) : ( + + Admin Only + + )} +
    + + {mounts === undefined || (error && isValidating) ? ( + + ) : length < 1 ? ( + + ) : null} +
    +
    +
    +
    +
    + ); +}; + +export default () => { + const hooks = useTableHooks(); + + return ( + + + + ); +}; diff --git a/resources/scripts/components/admin/mounts/NewMountContainer.tsx b/resources/scripts/components/admin/mounts/NewMountContainer.tsx new file mode 100644 index 0000000000..064b0942de --- /dev/null +++ b/resources/scripts/components/admin/mounts/NewMountContainer.tsx @@ -0,0 +1,51 @@ +import type { Actions } from 'easy-peasy'; +import { useStoreActions } from 'easy-peasy'; +import type { FormikHelpers } from 'formik'; +import { useNavigate } from 'react-router-dom'; +import tw from 'twin.macro'; + +import AdminContentBlock from '@/components/admin/AdminContentBlock'; +import FlashMessageRender from '@/components/FlashMessageRender'; +import MountForm from '@/components/admin/mounts/MountForm'; +import createMount from '@/api/admin/mounts/createMount'; +import type { ApplicationStore } from '@/state'; + +export default () => { + const navigate = useNavigate(); + + const { clearFlashes, clearAndAddHttpError } = useStoreActions( + (actions: Actions) => actions.flashes, + ); + + const submit = ( + { name, description, source, target, readOnly, userMountable }: any, + { setSubmitting }: FormikHelpers, + ) => { + clearFlashes('mount:create'); + + createMount(name, description, source, target, readOnly === '1', userMountable === '1') + .then(mount => navigate(`/admin/mounts/${mount.id}`)) + .catch(error => { + console.error(error); + clearAndAddHttpError({ key: 'mount:create', error }); + }) + .then(() => setSubmitting(false)); + }; + + return ( + +
    +
    +

    New Mount

    +

    + Add a new mount to the panel. +

    +
    +
    + + + + +
    + ); +}; diff --git a/resources/scripts/components/admin/nests/ImportEggButton.tsx b/resources/scripts/components/admin/nests/ImportEggButton.tsx new file mode 100644 index 0000000000..1dd6d2dd28 --- /dev/null +++ b/resources/scripts/components/admin/nests/ImportEggButton.tsx @@ -0,0 +1,82 @@ +import getEggs from '@/api/admin/nests/getEggs'; +import importEgg from '@/api/admin/nests/importEgg'; +import useFlash from '@/plugins/useFlash'; +// import { Editor } from '@/components/elements/editor'; +import { useState } from 'react'; +import Button from '@/components/elements/Button'; +import Modal from '@/components/elements/Modal'; +import FlashMessageRender from '@/components/FlashMessageRender'; +import { useParams } from 'react-router-dom'; +import tw from 'twin.macro'; + +export default ({ className }: { className?: string }) => { + const [visible, setVisible] = useState(false); + + const { clearFlashes } = useFlash(); + + const params = useParams<'nestId'>(); + const { mutate } = getEggs(Number(params.nestId)); + + let fetchFileContent: (() => Promise) | null = null; + + const submit = async () => { + clearFlashes('egg:import'); + + if (fetchFileContent === null) { + return; + } + + const egg = await importEgg(Number(params.nestId), await fetchFileContent()); + await mutate(data => ({ ...data!, items: [...data!.items!, egg] })); + setVisible(false); + }; + + return ( + <> + { + setVisible(false); + }} + > + + +

    Import Egg

    + + {/* {*/} + {/* fetchFileContent = value;*/} + {/* }}*/} + {/*/>*/} + +
    + + +
    +
    + + + + ); +}; diff --git a/resources/scripts/components/admin/nests/NestDeleteButton.tsx b/resources/scripts/components/admin/nests/NestDeleteButton.tsx new file mode 100644 index 0000000000..09d5733778 --- /dev/null +++ b/resources/scripts/components/admin/nests/NestDeleteButton.tsx @@ -0,0 +1,73 @@ +import type { Actions } from 'easy-peasy'; +import { useStoreActions } from 'easy-peasy'; +import { useState } from 'react'; +import tw from 'twin.macro'; + +import deleteNest from '@/api/admin/nests/deleteNest'; +import Button from '@/components/elements/Button'; +import ConfirmationModal from '@/components/elements/ConfirmationModal'; +import type { ApplicationStore } from '@/state'; + +interface Props { + nestId: number; + onDeleted: () => void; +} + +export default ({ nestId, onDeleted }: Props) => { + const [visible, setVisible] = useState(false); + const [loading, setLoading] = useState(false); + + const { clearFlashes, clearAndAddHttpError } = useStoreActions( + (actions: Actions) => actions.flashes, + ); + + const onDelete = () => { + setLoading(true); + clearFlashes('nest'); + + deleteNest(nestId) + .then(() => { + setLoading(false); + onDeleted(); + }) + .catch(error => { + console.error(error); + clearAndAddHttpError({ key: 'nest', error }); + + setLoading(false); + setVisible(false); + }); + }; + + return ( + <> + setVisible(false)} + > + Are you sure you want to delete this nest? Deleting a nest will delete all eggs assigned to it. + + + + + ); +}; diff --git a/resources/scripts/components/admin/nests/NestEditContainer.tsx b/resources/scripts/components/admin/nests/NestEditContainer.tsx new file mode 100644 index 0000000000..dabbead607 --- /dev/null +++ b/resources/scripts/components/admin/nests/NestEditContainer.tsx @@ -0,0 +1,250 @@ +import type { Action, Actions } from 'easy-peasy'; +import { action, createContextStore, useStoreActions } from 'easy-peasy'; +import type { FormikHelpers } from 'formik'; +import { Form, Formik } from 'formik'; +import { useEffect, useState } from 'react'; +import { NavLink, useNavigate, useParams } from 'react-router-dom'; +import tw from 'twin.macro'; +import { object, string } from 'yup'; + +import ImportEggButton from '@/components/admin/nests/ImportEggButton'; +import AdminContentBlock from '@/components/admin/AdminContentBlock'; +import Spinner from '@/components/elements/Spinner'; +import FlashMessageRender from '@/components/FlashMessageRender'; +import type { Nest } from '@/api/admin/nests/getNests'; +import getNest from '@/api/admin/nests/getNest'; +import updateNest from '@/api/admin/nests/updateNest'; +import Button from '@/components/elements/Button'; +import Field from '@/components/elements/Field'; +import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; +import AdminBox from '@/components/admin/AdminBox'; +import CopyOnClick from '@/components/elements/CopyOnClick'; +import Input from '@/components/elements/Input'; +import Label from '@/components/elements/Label'; +import NestDeleteButton from '@/components/admin/nests/NestDeleteButton'; +import NestEggTable from '@/components/admin/nests/NestEggTable'; +import type { ApplicationStore } from '@/state'; + +interface ctx { + nest: Nest | undefined; + setNest: Action; + + selectedEggs: number[]; + + setSelectedEggs: Action; + appendSelectedEggs: Action; + removeSelectedEggs: Action; +} + +export const Context = createContextStore({ + nest: undefined, + + setNest: action((state, payload) => { + state.nest = payload; + }), + + selectedEggs: [], + + setSelectedEggs: action((state, payload) => { + state.selectedEggs = payload; + }), + + appendSelectedEggs: action((state, payload) => { + state.selectedEggs = state.selectedEggs.filter(id => id !== payload).concat(payload); + }), + + removeSelectedEggs: action((state, payload) => { + state.selectedEggs = state.selectedEggs.filter(id => id !== payload); + }), +}); + +interface Values { + name: string; + description: string; +} + +const EditInformationContainer = () => { + const navigate = useNavigate(); + + const { clearFlashes, clearAndAddHttpError } = useStoreActions( + (actions: Actions) => actions.flashes, + ); + + const nest = Context.useStoreState(state => state.nest); + const setNest = Context.useStoreActions(actions => actions.setNest); + + if (nest === undefined) { + return <>; + } + + const submit = ({ name, description }: Values, { setSubmitting }: FormikHelpers) => { + clearFlashes('nest'); + + updateNest(nest.id, name, description) + .then(() => setNest({ ...nest, name, description })) + .catch(error => { + console.error(error); + clearAndAddHttpError({ key: 'nest', error }); + }) + .then(() => setSubmitting(false)); + }; + + return ( + + {({ isSubmitting, isValid }) => ( + <> + + + +
    + + + + +
    +
    + navigate('/admin/nests')} /> +
    + +
    + +
    +
    + +
    + + )} +
    + ); +}; + +const ViewDetailsContainer = () => { + const nest = Context.useStoreState(state => state.nest); + + if (nest === undefined) { + return <>; + } + + return ( + +
    +
    +
    + + + + +
    + +
    + + + + +
    + +
    + + + + +
    +
    +
    +
    + ); +}; + +const NestEditContainer = () => { + const params = useParams<'nestId'>(); + + const { clearFlashes, clearAndAddHttpError } = useStoreActions( + (actions: Actions) => actions.flashes, + ); + const [loading, setLoading] = useState(true); + + const nest = Context.useStoreState(state => state.nest); + const setNest = Context.useStoreActions(actions => actions.setNest); + + useEffect(() => { + clearFlashes('nest'); + + getNest(Number(params.nestId), ['eggs']) + .then(nest => setNest(nest)) + .catch(error => { + console.error(error); + clearAndAddHttpError({ key: 'nest', error }); + }) + .then(() => setLoading(false)); + }, []); + + if (loading || nest === undefined) { + return ( + + + +
    + +
    +
    + ); + } + + return ( + +
    +
    +

    {nest.name}

    + {(nest.description || '').length < 1 ? ( +

    + No description +

    + ) : ( +

    + {nest.description} +

    + )} +
    + +
    + + + + + +
    +
    + + + +
    + + +
    + + +
    + ); +}; + +export default () => { + return ( + + + + ); +}; diff --git a/resources/scripts/components/admin/nests/NestEggTable.tsx b/resources/scripts/components/admin/nests/NestEggTable.tsx new file mode 100644 index 0000000000..704dec1507 --- /dev/null +++ b/resources/scripts/components/admin/nests/NestEggTable.tsx @@ -0,0 +1,160 @@ +import type { ChangeEvent } from 'react'; +import { useContext, useEffect } from 'react'; +import { NavLink, useParams } from 'react-router-dom'; +import tw from 'twin.macro'; + +import type { Filters } from '@/api/admin/nests/getEggs'; +import getEggs, { Context as EggsContext } from '@/api/admin/nests/getEggs'; +import AdminCheckbox from '@/components/admin/AdminCheckbox'; +import AdminTable, { + TableBody, + TableHead, + TableHeader, + TableRow, + Pagination, + Loading, + NoItems, + ContentWrapper, + useTableHooks, +} from '@/components/admin/AdminTable'; +import { Context } from '@/components/admin/nests/NestEditContainer'; +import CopyOnClick from '@/components/elements/CopyOnClick'; +import useFlash from '@/plugins/useFlash'; + +const RowCheckbox = ({ id }: { id: number }) => { + const isChecked = Context.useStoreState(state => state.selectedEggs.indexOf(id) >= 0); + const appendSelectedEggs = Context.useStoreActions(actions => actions.appendSelectedEggs); + const removeSelectedEggs = Context.useStoreActions(actions => actions.removeSelectedEggs); + + return ( + ) => { + if (e.currentTarget.checked) { + appendSelectedEggs(id); + } else { + removeSelectedEggs(id); + } + }} + /> + ); +}; + +const EggsTable = () => { + const params = useParams<'nestId' | 'id'>(); + + const { page, setPage, setFilters, sort, setSort, sortDirection } = useContext(EggsContext); + const { clearFlashes, clearAndAddHttpError } = useFlash(); + const { data: eggs, error, isValidating } = getEggs(Number(params.nestId)); + + useEffect(() => { + if (!error) { + clearFlashes('nests'); + return; + } + + clearAndAddHttpError({ key: 'nests', error }); + }, [error]); + + const length = eggs?.items?.length || 0; + + const setSelectedEggs = Context.useStoreActions(actions => actions.setSelectedEggs); + const selectedEggsLength = Context.useStoreState(state => state.selectedEggs.length); + + const onSelectAllClick = (e: ChangeEvent) => { + setSelectedEggs(e.currentTarget.checked ? eggs?.items?.map(nest => nest.id) || [] : []); + }; + + const onSearch = (query: string): Promise => { + return new Promise(resolve => { + if (query.length < 2) { + setFilters(null); + } else { + setFilters({ name: query }); + } + return resolve(); + }); + }; + + useEffect(() => { + setSelectedEggs([]); + }, [page]); + + return ( + + + +
    + + + setSort('id')} + /> + setSort('name')} + /> + + + + + {eggs !== undefined && + !error && + !isValidating && + length > 0 && + eggs.items.map(egg => ( + + + + + + + + + + ))} + +
    + + + + + {egg.id} + + + + + {egg.name} + + + {egg.description} +
    + + {eggs === undefined || (error && isValidating) ? : length < 1 ? : null} +
    +
    +
    +
    + ); +}; + +export default () => { + const hooks = useTableHooks(); + + return ( + + + + ); +}; diff --git a/resources/scripts/components/admin/nests/NestsContainer.tsx b/resources/scripts/components/admin/nests/NestsContainer.tsx new file mode 100644 index 0000000000..2f937da806 --- /dev/null +++ b/resources/scripts/components/admin/nests/NestsContainer.tsx @@ -0,0 +1,182 @@ +import type { ChangeEvent } from 'react'; +import { useContext, useEffect } from 'react'; +import { NavLink } from 'react-router-dom'; +import tw from 'twin.macro'; + +import type { Filters } from '@/api/admin/nests/getNests'; +import getNests, { Context as NestsContext } from '@/api/admin/nests/getNests'; +import AdminContentBlock from '@/components/admin/AdminContentBlock'; +import AdminCheckbox from '@/components/admin/AdminCheckbox'; +import AdminTable, { + TableBody, + TableHead, + TableHeader, + TableRow, + Pagination, + Loading, + NoItems, + ContentWrapper, + useTableHooks, +} from '@/components/admin/AdminTable'; +import CopyOnClick from '@/components/elements/CopyOnClick'; +import NewNestButton from '@/components/admin/nests/NewNestButton'; +import FlashMessageRender from '@/components/FlashMessageRender'; +import useFlash from '@/plugins/useFlash'; +import { AdminContext } from '@/state/admin'; + +const RowCheckbox = ({ id }: { id: number }) => { + const isChecked = AdminContext.useStoreState(state => state.nests.selectedNests.indexOf(id) >= 0); + const appendSelectedNest = AdminContext.useStoreActions(actions => actions.nests.appendSelectedNest); + const removeSelectedNest = AdminContext.useStoreActions(actions => actions.nests.removeSelectedNest); + + return ( + ) => { + if (e.currentTarget.checked) { + appendSelectedNest(id); + } else { + removeSelectedNest(id); + } + }} + /> + ); +}; + +const NestsContainer = () => { + const { page, setPage, setFilters, sort, setSort, sortDirection } = useContext(NestsContext); + const { clearFlashes, clearAndAddHttpError } = useFlash(); + const { data: nests, error, isValidating } = getNests(); + + useEffect(() => { + if (!error) { + clearFlashes('nests'); + return; + } + + clearAndAddHttpError({ key: 'nests', error }); + }, [error]); + + const length = nests?.items?.length || 0; + + const setSelectedNests = AdminContext.useStoreActions(actions => actions.nests.setSelectedNests); + const selectedNestsLength = AdminContext.useStoreState(state => state.nests.selectedNests.length); + + const onSelectAllClick = (e: ChangeEvent) => { + setSelectedNests(e.currentTarget.checked ? nests?.items?.map(nest => nest.id) || [] : []); + }; + + const onSearch = (query: string): Promise => { + return new Promise(resolve => { + if (query.length < 2) { + setFilters(null); + } else { + setFilters({ name: query }); + } + return resolve(); + }); + }; + + useEffect(() => { + setSelectedNests([]); + }, [page]); + + return ( + +
    +
    +

    Nests

    +

    + All nests currently available on this system. +

    +
    + +
    + +
    +
    + + + + + + +
    + + + setSort('id')} + /> + setSort('name')} + /> + + + + + {nests !== undefined && + !error && + !isValidating && + length > 0 && + nests.items.map(nest => ( + + + + + + + + + + ))} + +
    + + + + + {nest.id} + + + + + {nest.name} + + + {nest.description} +
    + + {nests === undefined || (error && isValidating) ? ( + + ) : length < 1 ? ( + + ) : null} +
    +
    +
    +
    +
    + ); +}; + +export default () => { + const hooks = useTableHooks(); + + return ( + + + + ); +}; diff --git a/resources/scripts/components/admin/nests/NewEggContainer.tsx b/resources/scripts/components/admin/nests/NewEggContainer.tsx new file mode 100644 index 0000000000..1e79eb4ee7 --- /dev/null +++ b/resources/scripts/components/admin/nests/NewEggContainer.tsx @@ -0,0 +1,115 @@ +import type { FormikHelpers } from 'formik'; +import { Form, Formik } from 'formik'; +import { useRef } from 'react'; +import { useNavigate, useParams } from 'react-router-dom'; +import tw from 'twin.macro'; +import { object } from 'yup'; + +import createEgg from '@/api/admin/eggs/createEgg'; +import AdminContentBlock from '@/components/admin/AdminContentBlock'; +import { + EggImageContainer, + EggInformationContainer, + EggLifecycleContainer, + EggProcessContainer, + EggProcessContainerRef, + EggStartupContainer, +} from '@/components/admin/nests/eggs/EggSettingsContainer'; +import Button from '@/components/elements/Button'; +import FlashMessageRender from '@/components/FlashMessageRender'; +import useFlash from '@/plugins/useFlash'; + +interface Values { + name: string; + description: string; + startup: string; + dockerImages: string; + configStop: string; + configStartup: string; + configFiles: string; +} + +export default () => { + const navigate = useNavigate(); + const params = useParams<{ nestId: string }>(); + + const { clearFlashes, clearAndAddHttpError } = useFlash(); + + const ref = useRef(); + + const submit = async (values: Values, { setSubmitting }: FormikHelpers) => { + clearFlashes('egg:create'); + + const nestId = Number(params.nestId); + + values.configStartup = (await ref.current?.getStartupConfiguration()) || ''; + values.configFiles = (await ref.current?.getFilesConfiguration()) || ''; + + createEgg({ ...values, dockerImages: values.dockerImages.split('\n'), nestId }) + .then(egg => navigate(`/admin/nests/${nestId}/eggs/${egg.id}`)) + .catch(error => { + console.error(error); + clearAndAddHttpError({ key: 'egg:create', error }); + }) + .then(() => setSubmitting(false)); + }; + + return ( + +
    +
    +

    New Egg

    +

    + Add a new egg to the panel. +

    +
    +
    + + + + + {({ isSubmitting, isValid }) => ( +
    +
    + +
    + + + +
    + + +
    + + + +
    +
    + +
    +
    + + )} +
    +
    + ); +}; diff --git a/resources/scripts/components/admin/nests/NewNestButton.tsx b/resources/scripts/components/admin/nests/NewNestButton.tsx new file mode 100644 index 0000000000..60a6138409 --- /dev/null +++ b/resources/scripts/components/admin/nests/NewNestButton.tsx @@ -0,0 +1,112 @@ +import React, { useState } from 'react'; +import createNest from '@/api/admin/nests/createNest'; +import getNests from '@/api/admin/nests/getNests'; +import Button from '@/components/elements/Button'; +import Field from '@/components/elements/Field'; +import Modal from '@/components/elements/Modal'; +import FlashMessageRender from '@/components/FlashMessageRender'; +import useFlash from '@/plugins/useFlash'; +import { Form, Formik, FormikHelpers } from 'formik'; +import { object, string } from 'yup'; +import tw from 'twin.macro'; + +interface Values { + name: string, + description: string, +} + +const schema = object().shape({ + name: string() + .required('A nest name must be provided.') + .max(32, 'Nest name must not exceed 32 characters.'), + description: string() + .max(255, 'Nest description must not exceed 255 characters.'), +}); + +export default () => { + const [ visible, setVisible ] = useState(false); + const { clearFlashes, clearAndAddHttpError } = useFlash(); + const { mutate } = getNests(); + + const submit = ({ name, description }: Values, { setSubmitting }: FormikHelpers) => { + clearFlashes('nest:create'); + setSubmitting(true); + + createNest(name, description) + .then(async (nest) => { + await mutate(data => ({ ...data!, items: data!.items.concat(nest) }), false); + setVisible(false); + }) + .catch(error => { + clearAndAddHttpError({ key: 'nest:create', error }); + setSubmitting(false); + }); + }; + + return ( + <> + + { + ({ isSubmitting, resetForm }) => ( + { + resetForm(); + setVisible(false); + }} + > + + +

    New Nest

    + +
    + + +
    + +
    + +
    + + +
    + +
    + ) + } +
    + + + + ); +}; diff --git a/resources/scripts/components/admin/nests/eggs/EggDeleteButton.tsx b/resources/scripts/components/admin/nests/eggs/EggDeleteButton.tsx new file mode 100644 index 0000000000..11a141693d --- /dev/null +++ b/resources/scripts/components/admin/nests/eggs/EggDeleteButton.tsx @@ -0,0 +1,73 @@ +import type { Actions } from 'easy-peasy'; +import { useStoreActions } from 'easy-peasy'; +import { useState } from 'react'; +import tw from 'twin.macro'; + +import deleteEgg from '@/api/admin/eggs/deleteEgg'; +import Button from '@/components/elements/Button'; +import ConfirmationModal from '@/components/elements/ConfirmationModal'; +import type { ApplicationStore } from '@/state'; + +interface Props { + eggId: number; + onDeleted: () => void; +} + +export default ({ eggId, onDeleted }: Props) => { + const [visible, setVisible] = useState(false); + const [loading, setLoading] = useState(false); + + const { clearFlashes, clearAndAddHttpError } = useStoreActions( + (actions: Actions) => actions.flashes, + ); + + const onDelete = () => { + setLoading(true); + clearFlashes('egg'); + + deleteEgg(eggId) + .then(() => { + setLoading(false); + onDeleted(); + }) + .catch(error => { + console.error(error); + clearAndAddHttpError({ key: 'egg', error }); + + setLoading(false); + setVisible(false); + }); + }; + + return ( + <> + setVisible(false)} + > + Are you sure you want to delete this egg? You may only delete an egg with no servers using it. + + + + + ); +}; diff --git a/resources/scripts/components/admin/nests/eggs/EggExportButton.tsx b/resources/scripts/components/admin/nests/eggs/EggExportButton.tsx new file mode 100644 index 0000000000..513bc972d9 --- /dev/null +++ b/resources/scripts/components/admin/nests/eggs/EggExportButton.tsx @@ -0,0 +1,85 @@ +import { exportEgg } from '@/api/admin/egg'; +import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; +import useFlash from '@/plugins/useFlash'; +// import { jsonLanguage } from '@codemirror/lang-json'; +// import Editor from '@/components/elements/Editor'; +import { useEffect, useState } from 'react'; +import Button from '@/components/elements/Button'; +import Modal from '@/components/elements/Modal'; +import FlashMessageRender from '@/components/FlashMessageRender'; +import { useParams } from 'react-router-dom'; +import tw from 'twin.macro'; + +export default ({ className }: { className?: string }) => { + const params = useParams<'id'>(); + const { clearAndAddHttpError, clearFlashes } = useFlash(); + + const [visible, setVisible] = useState(false); + const [loading, setLoading] = useState(true); + const [_content, setContent] = useState | null>(null); + + useEffect(() => { + if (!visible) { + return; + } + + clearFlashes('egg:export'); + setLoading(true); + + exportEgg(Number(params.id)) + .then(setContent) + .catch(error => clearAndAddHttpError({ key: 'egg:export', error })) + .then(() => setLoading(false)); + }, [visible]); + + return ( + <> + { + setVisible(false); + }} + css={tw`relative`} + > + +

    Export Egg

    + + + {/**/} + +
    + + +
    +
    + + + + ); +}; diff --git a/resources/scripts/components/admin/nests/eggs/EggInstallContainer.tsx b/resources/scripts/components/admin/nests/eggs/EggInstallContainer.tsx new file mode 100644 index 0000000000..3a9626c386 --- /dev/null +++ b/resources/scripts/components/admin/nests/eggs/EggInstallContainer.tsx @@ -0,0 +1,110 @@ +import { useEggFromRoute } from '@/api/admin/egg'; +import updateEgg from '@/api/admin/eggs/updateEgg'; +import Field from '@/components/elements/Field'; +import useFlash from '@/plugins/useFlash'; +// import { shell } from '@codemirror/legacy-modes/mode/shell'; +import { faScroll } from '@fortawesome/free-solid-svg-icons'; +import { Form, Formik, FormikHelpers } from 'formik'; +import tw from 'twin.macro'; +import AdminBox from '@/components/admin/AdminBox'; +import Button from '@/components/elements/Button'; +// import Editor from '@/components/elements/Editor'; +import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; + +interface Values { + scriptContainer: string; + scriptEntry: string; + scriptInstall: string; +} + +export default function EggInstallContainer() { + const { clearFlashes, clearAndAddHttpError } = useFlash(); + + const { data: egg } = useEggFromRoute(); + + if (!egg) { + return null; + } + + let fetchFileContent: (() => Promise) | null = null; + + const submit = async (values: Values, { setSubmitting }: FormikHelpers) => { + if (fetchFileContent === null) { + return; + } + + values.scriptInstall = await fetchFileContent(); + + clearFlashes('egg'); + + updateEgg(egg.id, values) + .catch(error => { + console.error(error); + clearAndAddHttpError({ key: 'egg', error }); + }) + .then(() => setSubmitting(false)); + }; + + return ( + + {({ isSubmitting, isValid }) => ( + +
    + + +
    + {/* {*/} + {/* fetchFileContent = value;*/} + {/* }}*/} + {/*/>*/} + +
    +
    + + + +
    +
    + +
    + +
    + +
    +
    + )} +
    + ); +} diff --git a/resources/scripts/components/admin/nests/eggs/EggRouter.tsx b/resources/scripts/components/admin/nests/eggs/EggRouter.tsx new file mode 100644 index 0000000000..9bf84ac3cf --- /dev/null +++ b/resources/scripts/components/admin/nests/eggs/EggRouter.tsx @@ -0,0 +1,90 @@ +import { useEffect } from 'react'; +import { Route, Routes, useParams } from 'react-router-dom'; +import tw from 'twin.macro'; + +import { useEggFromRoute } from '@/api/admin/egg'; +import EggInstallContainer from '@/components/admin/nests/eggs/EggInstallContainer'; +import EggVariablesContainer from '@/components/admin/nests/eggs/EggVariablesContainer'; +import useFlash from '@/plugins/useFlash'; +import AdminContentBlock from '@/components/admin/AdminContentBlock'; +import Spinner from '@/components/elements/Spinner'; +import FlashMessageRender from '@/components/FlashMessageRender'; +import { SubNavigation, SubNavigationLink } from '@/components/admin/SubNavigation'; +import EggSettingsContainer from '@/components/admin/nests/eggs/EggSettingsContainer'; + +const EggRouter = () => { + const { id, nestId } = useParams<'nestId' | 'id'>(); + + const { clearFlashes, clearAndAddHttpError } = useFlash(); + const { data: egg, error, isValidating, mutate } = useEggFromRoute(); + + useEffect(() => { + mutate(); + }, []); + + useEffect(() => { + if (!error) clearFlashes('egg'); + if (error) clearAndAddHttpError({ key: 'egg', error }); + }, [error]); + + if (!egg || (error && isValidating)) { + return ( + + + + ); + } + + return ( + +
    +
    +

    {egg.name}

    +

    + {egg.uuid} +

    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + } /> + } /> + } /> + +
    + ); +}; + +export default () => { + return ; +}; diff --git a/resources/scripts/components/admin/nests/eggs/EggSettingsContainer.tsx b/resources/scripts/components/admin/nests/eggs/EggSettingsContainer.tsx new file mode 100644 index 0000000000..314b02cba4 --- /dev/null +++ b/resources/scripts/components/admin/nests/eggs/EggSettingsContainer.tsx @@ -0,0 +1,245 @@ +import { useEggFromRoute } from '@/api/admin/egg'; +import updateEgg from '@/api/admin/eggs/updateEgg'; +import EggDeleteButton from '@/components/admin/nests/eggs/EggDeleteButton'; +import EggExportButton from '@/components/admin/nests/eggs/EggExportButton'; +import Button from '@/components/elements/Button'; +// import Editor from '@/components/elements/Editor'; +import Field, { TextareaField } from '@/components/elements/Field'; +import Input from '@/components/elements/Input'; +import Label from '@/components/elements/Label'; +import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; +import useFlash from '@/plugins/useFlash'; +// import { jsonLanguage } from '@codemirror/lang-json'; +import { faDocker } from '@fortawesome/free-brands-svg-icons'; +import { faEgg, faFireAlt, faMicrochip, faTerminal } from '@fortawesome/free-solid-svg-icons'; +import { forwardRef, useImperativeHandle, useRef } from 'react'; +import AdminBox from '@/components/admin/AdminBox'; +import { useNavigate } from 'react-router-dom'; +import tw from 'twin.macro'; +import { object } from 'yup'; +import { Form, Formik, FormikHelpers, useFormikContext } from 'formik'; + +export function EggInformationContainer() { + const { isSubmitting } = useFormikContext(); + + return ( + + + + + + + + ); +} + +function EggDetailsContainer() { + const { data: egg } = useEggFromRoute(); + + if (!egg) { + return null; + } + + return ( + +
    + + +
    + +
    + + +
    +
    + ); +} + +export function EggStartupContainer({ className }: { className?: string }) { + const { isSubmitting } = useFormikContext(); + + return ( + + + + + + ); +} + +export function EggImageContainer() { + const { isSubmitting } = useFormikContext(); + + return ( + + + + + + ); +} + +export function EggLifecycleContainer() { + const { isSubmitting } = useFormikContext(); + + return ( + + + + + + ); +} + +interface EggProcessContainerProps { + className?: string; +} + +export interface EggProcessContainerRef { + getStartupConfiguration: () => Promise; + getFilesConfiguration: () => Promise; +} + +export const EggProcessContainer = forwardRef(function EggProcessContainer( + { className }, + ref, +) { + // const { isSubmitting, values } = useFormikContext(); + const { isSubmitting } = useFormikContext(); + + let fetchStartupConfiguration: (() => Promise) | null = null; + let fetchFilesConfiguration: (() => Promise) | null = null; + + useImperativeHandle(ref, () => ({ + getStartupConfiguration: async () => { + if (fetchStartupConfiguration === null) { + return new Promise(resolve => resolve(null)); + } + return await fetchStartupConfiguration(); + }, + + getFilesConfiguration: async () => { + if (fetchFilesConfiguration === null) { + return new Promise(resolve => resolve(null)); + } + return await fetchFilesConfiguration(); + }, + })); + + return ( + + + +
    + + {/* {*/} + {/* fetchStartupConfiguration = value;*/} + {/* }}*/} + {/*/>*/} +
    + +
    + + {/* {*/} + {/* fetchFilesConfiguration = value;*/} + {/* }}*/} + {/*/>*/} +
    +
    + ); +}); + +interface Values { + name: string; + description: string; + startup: string; + dockerImages: string; + configStop: string; + configStartup: string; + configFiles: string; +} + +export default function EggSettingsContainer() { + const navigate = useNavigate(); + + const ref = useRef(); + + const { clearFlashes, clearAndAddHttpError } = useFlash(); + + const { data: egg } = useEggFromRoute(); + + if (!egg) { + return null; + } + + const submit = async (values: Values, { setSubmitting }: FormikHelpers) => { + clearFlashes('egg'); + + values.configStartup = (await ref.current?.getStartupConfiguration()) || ''; + values.configFiles = (await ref.current?.getFilesConfiguration()) || ''; + + updateEgg(egg.id, { + ...values, + // TODO + dockerImages: {}, + }) + .catch(error => { + console.error(error); + clearAndAddHttpError({ key: 'egg', error }); + }) + .then(() => setSubmitting(false)); + }; + + return ( + + {({ isSubmitting, isValid }) => ( +
    +
    + + +
    + + + +
    + + +
    + + + +
    +
    + navigate('/admin/nests')} /> + + +
    +
    + + )} +
    + ); +} diff --git a/resources/scripts/components/admin/nests/eggs/EggVariablesContainer.tsx b/resources/scripts/components/admin/nests/eggs/EggVariablesContainer.tsx new file mode 100644 index 0000000000..c6cbfe9258 --- /dev/null +++ b/resources/scripts/components/admin/nests/eggs/EggVariablesContainer.tsx @@ -0,0 +1,218 @@ +import { TrashIcon } from '@heroicons/react/outline'; +import type { FormikHelpers } from 'formik'; +import { Form, Formik, useFormikContext } from 'formik'; +import { useState } from 'react'; +import tw from 'twin.macro'; +import { array, boolean, object, string } from 'yup'; + +import deleteEggVariable from '@/api/admin/eggs/deleteEggVariable'; +import updateEggVariables from '@/api/admin/eggs/updateEggVariables'; +import { NoItems } from '@/components/admin/AdminTable'; +import ConfirmationModal from '@/components/elements/ConfirmationModal'; +import type { EggVariable } from '@/api/admin/egg'; +import { useEggFromRoute } from '@/api/admin/egg'; +import NewVariableButton from '@/components/admin/nests/eggs/NewVariableButton'; +import AdminBox from '@/components/admin/AdminBox'; +import Button from '@/components/elements/Button'; +import Checkbox from '@/components/elements/Checkbox'; +import Field, { FieldRow, TextareaField } from '@/components/elements/Field'; +import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; +import useFlash from '@/plugins/useFlash'; + +export const validationSchema = object().shape({ + name: string().required().min(1).max(191), + description: string(), + environmentVariable: string().required().min(1).max(191), + defaultValue: string(), + isUserViewable: boolean().required(), + isUserEditable: boolean().required(), + rules: string().required(), +}); + +export function EggVariableForm({ prefix }: { prefix: string }) { + return ( + <> + + + + + + + + + + +
    + + + +
    + + + + ); +} + +function EggVariableDeleteButton({ onClick }: { onClick: (success: () => void) => void }) { + const [visible, setVisible] = useState(false); + const [loading, setLoading] = useState(false); + + const onDelete = () => { + setLoading(true); + + onClick(() => { + //setLoading(false); + }); + }; + + return ( + <> + setVisible(false)} + > + Are you sure you want to delete this variable? Deleting this variable will delete it from every server + using this egg. + + + + + ); +} + +function EggVariableBox({ + onDeleteClick, + variable, + prefix, +}: { + onDeleteClick: (success: () => void) => void; + variable: EggVariable; + prefix: string; +}) { + const { isSubmitting } = useFormikContext(); + + return ( + {variable.name}

    } + button={} + > + + + +
    + ); +} + +export default function EggVariablesContainer() { + const { clearAndAddHttpError } = useFlash(); + + const { data: egg, mutate } = useEggFromRoute(); + + if (!egg) { + return null; + } + + const submit = (values: EggVariable[], { setSubmitting }: FormikHelpers) => { + updateEggVariables(egg.id, values) + .then(async () => await mutate()) + .catch(error => clearAndAddHttpError({ key: 'egg', error })) + .then(() => setSubmitting(false)); + }; + + return ( + + {({ isSubmitting, isValid }) => ( +
    +
    + {egg.relationships.variables?.length === 0 ? ( + + ) : ( +
    + {egg.relationships.variables.map((v, i) => ( + { + deleteEggVariable(egg.id, v.id) + .then(async () => { + await mutate(egg => ({ + ...egg!, + relationships: { + ...egg!.relationships, + variables: egg!.relationships.variables!.filter( + v2 => v.id === v2.id, + ), + }, + })); + success(); + }) + .catch(error => clearAndAddHttpError({ key: 'egg', error })); + }} + /> + ))} +
    + )} + +
    +
    + + + +
    +
    +
    +
    + )} +
    + ); +} diff --git a/resources/scripts/components/admin/nests/eggs/NewVariableButton.tsx b/resources/scripts/components/admin/nests/eggs/NewVariableButton.tsx new file mode 100644 index 0000000000..fd9057f3c2 --- /dev/null +++ b/resources/scripts/components/admin/nests/eggs/NewVariableButton.tsx @@ -0,0 +1,103 @@ +import type { FormikHelpers } from 'formik'; +import { Form, Formik, useFormikContext } from 'formik'; +import { useState } from 'react'; +import tw from 'twin.macro'; + +import type { CreateEggVariable } from '@/api/admin/eggs/createEggVariable'; +import createEggVariable from '@/api/admin/eggs/createEggVariable'; +import { useEggFromRoute } from '@/api/admin/egg'; +import { EggVariableForm, validationSchema } from '@/components/admin/nests/eggs/EggVariablesContainer'; +import Modal from '@/components/elements/Modal'; +import FlashMessageRender from '@/components/FlashMessageRender'; +import Button from '@/components/elements/Button'; +import useFlash from '@/plugins/useFlash'; + +export default function NewVariableButton() { + const { setValues } = useFormikContext(); + const [visible, setVisible] = useState(false); + const { clearFlashes, clearAndAddHttpError } = useFlash(); + + const { data: egg, mutate } = useEggFromRoute(); + + if (!egg) { + return null; + } + + const submit = (values: CreateEggVariable, { setSubmitting }: FormikHelpers) => { + clearFlashes('variable:create'); + + createEggVariable(egg.id, values) + .then(async variable => { + setValues([...egg.relationships.variables, variable]); + await mutate(egg => ({ + ...egg!, + relationships: { ...egg!.relationships, variables: [...egg!.relationships.variables, variable] }, + })); + setVisible(false); + }) + .catch(error => { + clearAndAddHttpError({ key: 'variable:create', error }); + setSubmitting(false); + }); + }; + + return ( + <> + + {({ isSubmitting, isValid, resetForm }) => ( + { + resetForm(); + setVisible(false); + }} + > + + +

    New Variable

    + +
    + + +
    + + +
    + +
    + )} +
    + + + + ); +} diff --git a/resources/scripts/components/admin/nodes/DatabaseSelect.tsx b/resources/scripts/components/admin/nodes/DatabaseSelect.tsx new file mode 100644 index 0000000000..e032ec16ca --- /dev/null +++ b/resources/scripts/components/admin/nodes/DatabaseSelect.tsx @@ -0,0 +1,56 @@ +import { useFormikContext } from 'formik'; +import { useState } from 'react'; + +import type { Database } from '@/api/admin/databases/getDatabases'; +import searchDatabases from '@/api/admin/databases/searchDatabases'; +import SearchableSelect, { Option } from '@/components/elements/SearchableSelect'; + +export default ({ selected }: { selected: Database | null }) => { + const context = useFormikContext(); + + const [database, setDatabase] = useState(selected); + const [databases, setDatabases] = useState(null); + + const onSearch = (query: string): Promise => { + return new Promise((resolve, reject) => { + searchDatabases({ name: query }) + .then(databases => { + setDatabases(databases); + return resolve(); + }) + .catch(reject); + }); + }; + + const onSelect = (database: Database | null) => { + setDatabase(database); + context.setFieldValue('databaseHostId', database?.id || null); + }; + + const getSelectedText = (database: Database | null): string | undefined => { + return database?.name; + }; + + return ( + + {databases?.map(d => ( + + ))} + + ); +}; diff --git a/resources/scripts/components/admin/nodes/LocationSelect.tsx b/resources/scripts/components/admin/nodes/LocationSelect.tsx new file mode 100644 index 0000000000..0bf38f46e5 --- /dev/null +++ b/resources/scripts/components/admin/nodes/LocationSelect.tsx @@ -0,0 +1,56 @@ +import { useFormikContext } from 'formik'; +import { useState } from 'react'; + +import type { Location } from '@/api/admin/locations/getLocations'; +import searchLocations from '@/api/admin/locations/searchLocations'; +import SearchableSelect, { Option } from '@/components/elements/SearchableSelect'; + +export default ({ selected }: { selected: Location | null }) => { + const context = useFormikContext(); + + const [location, setLocation] = useState(selected); + const [locations, setLocations] = useState(null); + + const onSearch = (query: string): Promise => { + return new Promise((resolve, reject) => { + searchLocations({ short: query }) + .then(locations => { + setLocations(locations); + return resolve(); + }) + .catch(reject); + }); + }; + + const onSelect = (location: Location | null) => { + setLocation(location); + context.setFieldValue('locationId', location?.id || null); + }; + + const getSelectedText = (location: Location | null): string | undefined => { + return location?.short; + }; + + return ( + + {locations?.map(d => ( + + ))} + + ); +}; diff --git a/resources/scripts/components/admin/nodes/NewNodeContainer.tsx b/resources/scripts/components/admin/nodes/NewNodeContainer.tsx new file mode 100644 index 0000000000..e98ba4bd88 --- /dev/null +++ b/resources/scripts/components/admin/nodes/NewNodeContainer.tsx @@ -0,0 +1,127 @@ +import type { Actions } from 'easy-peasy'; +import { useStoreActions } from 'easy-peasy'; +import type { FormikHelpers } from 'formik'; +import { Form, Formik } from 'formik'; +import { useNavigate } from 'react-router-dom'; +import tw from 'twin.macro'; +import { number, object, string } from 'yup'; + +import type { Values } from '@/api/admin/nodes/createNode'; +import createNode from '@/api/admin/nodes/createNode'; +import AdminContentBlock from '@/components/admin/AdminContentBlock'; +import NodeLimitContainer from '@/components/admin/nodes/NodeLimitContainer'; +import NodeListenContainer from '@/components/admin/nodes/NodeListenContainer'; +import NodeSettingsContainer from '@/components/admin/nodes/NodeSettingsContainer'; +import Button from '@/components/elements/Button'; +import FlashMessageRender from '@/components/FlashMessageRender'; +import type { ApplicationStore } from '@/state'; + +type Values2 = Omit, 'public'> & { behindProxy: string; public: string }; + +const initialValues: Values2 = { + name: '', + locationId: 0, + databaseHostId: null, + fqdn: '', + scheme: 'https', + behindProxy: 'false', + public: 'true', + daemonBase: '/var/lib/pterodactyl/volumes', + + listenPortHTTP: 8080, + publicPortHTTP: 8080, + listenPortSFTP: 2022, + publicPortSFTP: 2022, + + memory: 0, + memoryOverallocate: 0, + disk: 0, + diskOverallocate: 0, +}; + +export default () => { + const navigate = useNavigate(); + + const { clearFlashes, clearAndAddHttpError } = useStoreActions( + (actions: Actions) => actions.flashes, + ); + + const submit = (values2: Values2, { setSubmitting }: FormikHelpers) => { + clearFlashes('node:create'); + + const values: Values = { + ...values2, + behindProxy: values2.behindProxy === 'true', + public: values2.public === 'true', + }; + + createNode(values) + .then(node => navigate(`/admin/nodes/${node.id}`)) + .catch(error => { + console.error(error); + clearAndAddHttpError({ key: 'node:create', error }); + }) + .then(() => setSubmitting(false)); + }; + + return ( + +
    +
    +

    New Node

    +

    + Add a new node to the panel. +

    +
    +
    + + + + + {({ isSubmitting, isValid }) => ( +
    +
    +
    + +
    + +
    +
    + +
    + +
    + +
    + +
    +
    + +
    +
    +
    +
    +
    + )} +
    +
    + ); +}; diff --git a/resources/scripts/components/admin/nodes/NodeAboutContainer.tsx b/resources/scripts/components/admin/nodes/NodeAboutContainer.tsx new file mode 100644 index 0000000000..bda7c8d776 --- /dev/null +++ b/resources/scripts/components/admin/nodes/NodeAboutContainer.tsx @@ -0,0 +1,96 @@ +import type { Actions } from 'easy-peasy'; +import { useStoreActions } from 'easy-peasy'; +import type { ReactNode } from 'react'; +import { useEffect, useState } from 'react'; +import tw from 'twin.macro'; + +import type { NodeInformation } from '@/api/admin/nodes/getNodeInformation'; +import getNodeInformation from '@/api/admin/nodes/getNodeInformation'; +import AdminBox from '@/components/admin/AdminBox'; +import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; +import { Context } from '@/components/admin/nodes/NodeRouter'; +import type { ApplicationStore } from '@/state'; + +const Code = ({ className, children }: { className?: string; children: ReactNode }) => { + return ( + + {children} + + ); +}; + +export default () => { + const { clearFlashes, clearAndAddHttpError } = useStoreActions( + (actions: Actions) => actions.flashes, + ); + + const [loading, setLoading] = useState(true); + const [info, setInfo] = useState(null); + + const node = Context.useStoreState(state => state.node); + + if (node === undefined) { + return <>; + } + + useEffect(() => { + clearFlashes('node'); + + getNodeInformation(node.id) + .then(info => setInfo(info)) + .catch(error => { + console.error(error); + clearAndAddHttpError({ key: 'node', error }); + }) + .then(() => setLoading(false)); + }, []); + + if (loading) { + return ( + + + + ); + } + + return ( + + + + + + + + + + + + + + + + + + + + + + + + +
    Wings Version + {info?.version} +
    Operating System + {info?.system.type} +
    Architecture + {info?.system.arch} +
    Kernel + {info?.system.release} +
    CPU Threads + {info?.system.cpus} +
    + + {/* TODO: Description code-block with edit option */} +
    + ); +}; diff --git a/resources/scripts/components/admin/nodes/NodeAllocationContainer.tsx b/resources/scripts/components/admin/nodes/NodeAllocationContainer.tsx new file mode 100644 index 0000000000..b8c47e3d4a --- /dev/null +++ b/resources/scripts/components/admin/nodes/NodeAllocationContainer.tsx @@ -0,0 +1,27 @@ +import { faNetworkWired } from '@fortawesome/free-solid-svg-icons'; +import { useParams } from 'react-router-dom'; +import tw from 'twin.macro'; + +import AdminBox from '@/components/admin/AdminBox'; +import AllocationTable from '@/components/admin/nodes/allocations/AllocationTable'; +import CreateAllocationForm from '@/components/admin/nodes/allocations/CreateAllocationForm'; + +export default () => { + const params = useParams<'id'>(); + + return ( + <> +
    +
    + +
    + +
    + + + +
    +
    + + ); +}; diff --git a/resources/scripts/components/admin/nodes/NodeConfigurationContainer.tsx b/resources/scripts/components/admin/nodes/NodeConfigurationContainer.tsx new file mode 100644 index 0000000000..e8ca1d9780 --- /dev/null +++ b/resources/scripts/components/admin/nodes/NodeConfigurationContainer.tsx @@ -0,0 +1,70 @@ +import { faCode, faDragon } from '@fortawesome/free-solid-svg-icons'; +import type { Actions } from 'easy-peasy'; +import { useStoreActions } from 'easy-peasy'; +import { useEffect, useState } from 'react'; +import tw from 'twin.macro'; + +import getNodeConfiguration from '@/api/admin/nodes/getNodeConfiguration'; +import AdminBox from '@/components/admin/AdminBox'; +import { Context } from '@/components/admin/nodes/NodeRouter'; +import CopyOnClick from '@/components/elements/CopyOnClick'; +import type { ApplicationStore } from '@/state'; + +export default () => { + const { clearFlashes, clearAndAddHttpError } = useStoreActions( + (actions: Actions) => actions.flashes, + ); + + const [configuration, setConfiguration] = useState(''); + + const node = Context.useStoreState(state => state.node); + + if (node === undefined) { + return <>; + } + + useEffect(() => { + clearFlashes('node'); + + getNodeConfiguration(node.id) + .then(configuration => setConfiguration(configuration)) + .catch(error => { + console.error(error); + clearAndAddHttpError({ key: 'node', error }); + }); + }, []); + + return ( + <> + +
    +
    + + + + + +
    +
    +                        {configuration}
    +                    
    +
    +
    + + + Never™ + + + ); +}; diff --git a/resources/scripts/components/admin/nodes/NodeDeleteButton.tsx b/resources/scripts/components/admin/nodes/NodeDeleteButton.tsx new file mode 100644 index 0000000000..aafad3ef25 --- /dev/null +++ b/resources/scripts/components/admin/nodes/NodeDeleteButton.tsx @@ -0,0 +1,73 @@ +import type { Actions } from 'easy-peasy'; +import { useStoreActions } from 'easy-peasy'; +import { useState } from 'react'; +import tw from 'twin.macro'; + +import deleteNode from '@/api/admin/nodes/deleteNode'; +import Button from '@/components/elements/Button'; +import ConfirmationModal from '@/components/elements/ConfirmationModal'; +import type { ApplicationStore } from '@/state'; + +interface Props { + nodeId: number; + onDeleted: () => void; +} + +export default ({ nodeId, onDeleted }: Props) => { + const [visible, setVisible] = useState(false); + const [loading, setLoading] = useState(false); + + const { clearFlashes, clearAndAddHttpError } = useStoreActions( + (actions: Actions) => actions.flashes, + ); + + const onDelete = () => { + setLoading(true); + clearFlashes('node'); + + deleteNode(nodeId) + .then(() => { + setLoading(false); + onDeleted(); + }) + .catch(error => { + console.error(error); + clearAndAddHttpError({ key: 'node', error }); + + setLoading(false); + setVisible(false); + }); + }; + + return ( + <> + setVisible(false)} + > + Are you sure you want to delete this node? + + + + + ); +}; diff --git a/resources/scripts/components/admin/nodes/NodeEditContainer.tsx b/resources/scripts/components/admin/nodes/NodeEditContainer.tsx new file mode 100644 index 0000000000..dc7280fef5 --- /dev/null +++ b/resources/scripts/components/admin/nodes/NodeEditContainer.tsx @@ -0,0 +1,134 @@ +import type { Actions } from 'easy-peasy'; +import { useStoreActions } from 'easy-peasy'; +import type { FormikHelpers } from 'formik'; +import { Form, Formik } from 'formik'; +import { useNavigate } from 'react-router-dom'; +import tw from 'twin.macro'; +import { number, object, string } from 'yup'; + +import updateNode from '@/api/admin/nodes/updateNode'; +import NodeDeleteButton from '@/components/admin/nodes/NodeDeleteButton'; +import NodeLimitContainer from '@/components/admin/nodes/NodeLimitContainer'; +import NodeListenContainer from '@/components/admin/nodes/NodeListenContainer'; +import { Context } from '@/components/admin/nodes/NodeRouter'; +import NodeSettingsContainer from '@/components/admin/nodes/NodeSettingsContainer'; +import Button from '@/components/elements/Button'; +import type { ApplicationStore } from '@/state'; + +interface Values { + name: string; + locationId: number; + databaseHostId: number | null; + fqdn: string; + scheme: string; + behindProxy: string; // Yes, this is technically a boolean. + public: string; // Yes, this is technically a boolean. + daemonBase: string; // This value cannot be updated once a node has been created. + + memory: number; + memoryOverallocate: number; + disk: number; + diskOverallocate: number; + + listenPortHTTP: number; + publicPortHTTP: number; + listenPortSFTP: number; + publicPortSFTP: number; +} + +export default () => { + const navigate = useNavigate(); + + const { clearFlashes, clearAndAddHttpError } = useStoreActions( + (actions: Actions) => actions.flashes, + ); + + const node = Context.useStoreState(state => state.node); + const setNode = Context.useStoreActions(actions => actions.setNode); + + if (node === undefined) { + return <>; + } + + const submit = (values: Values, { setSubmitting }: FormikHelpers) => { + clearFlashes('node'); + + const v = { ...values, behindProxy: values.behindProxy === 'true', public: values.public === 'true' }; + + updateNode(node.id, v) + .then(() => setNode({ ...node, ...v })) + .catch(error => { + console.error(error); + clearAndAddHttpError({ key: 'node', error }); + }) + .then(() => setSubmitting(false)); + }; + + return ( + + {({ isSubmitting, isValid }) => ( +
    +
    +
    + +
    + +
    +
    + +
    + +
    + +
    + +
    +
    + navigate('/admin/nodes')} /> + +
    +
    +
    +
    +
    + )} +
    + ); +}; diff --git a/resources/scripts/components/admin/nodes/NodeLimitContainer.tsx b/resources/scripts/components/admin/nodes/NodeLimitContainer.tsx new file mode 100644 index 0000000000..4ef5c3a1f7 --- /dev/null +++ b/resources/scripts/components/admin/nodes/NodeLimitContainer.tsx @@ -0,0 +1,47 @@ +import { faMicrochip } from '@fortawesome/free-solid-svg-icons'; +import { useFormikContext } from 'formik'; +import tw from 'twin.macro'; + +import AdminBox from '@/components/admin/AdminBox'; +import Field from '@/components/elements/Field'; +import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; + +export default () => { + const { isSubmitting } = useFormikContext(); + + return ( + + + +
    +
    + +
    + +
    + +
    +
    + +
    +
    + +
    + +
    + +
    +
    +
    + ); +}; diff --git a/resources/scripts/components/admin/nodes/NodeListenContainer.tsx b/resources/scripts/components/admin/nodes/NodeListenContainer.tsx new file mode 100644 index 0000000000..7c1baa27d7 --- /dev/null +++ b/resources/scripts/components/admin/nodes/NodeListenContainer.tsx @@ -0,0 +1,37 @@ +import { faNetworkWired } from '@fortawesome/free-solid-svg-icons'; +import { useFormikContext } from 'formik'; +import tw from 'twin.macro'; + +import AdminBox from '@/components/admin/AdminBox'; +import Field from '@/components/elements/Field'; +import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; + +export default () => { + const { isSubmitting } = useFormikContext(); + + return ( + + + +
    +
    + +
    + +
    + +
    +
    + +
    +
    + +
    + +
    + +
    +
    +
    + ); +}; diff --git a/resources/scripts/components/admin/nodes/NodeRouter.tsx b/resources/scripts/components/admin/nodes/NodeRouter.tsx new file mode 100644 index 0000000000..a3ee6b2d94 --- /dev/null +++ b/resources/scripts/components/admin/nodes/NodeRouter.tsx @@ -0,0 +1,146 @@ +import type { Action, Actions } from 'easy-peasy'; +import { action, createContextStore, useStoreActions } from 'easy-peasy'; +import { useEffect, useState } from 'react'; +import { Route, Routes, useParams } from 'react-router-dom'; +import tw from 'twin.macro'; + +import type { Node } from '@/api/admin/nodes/getNodes'; +import getNode from '@/api/admin/nodes/getNode'; +import FlashMessageRender from '@/components/FlashMessageRender'; +import AdminContentBlock from '@/components/admin/AdminContentBlock'; +import NodeEditContainer from '@/components/admin/nodes/NodeEditContainer'; +import Spinner from '@/components/elements/Spinner'; +import { SubNavigation, SubNavigationLink } from '@/components/admin/SubNavigation'; +import NodeAboutContainer from '@/components/admin/nodes/NodeAboutContainer'; +import NodeConfigurationContainer from '@/components/admin/nodes/NodeConfigurationContainer'; +import NodeAllocationContainer from '@/components/admin/nodes/NodeAllocationContainer'; +import NodeServers from '@/components/admin/nodes/NodeServers'; +import type { ApplicationStore } from '@/state'; + +interface ctx { + node: Node | undefined; + setNode: Action; +} + +export const Context = createContextStore({ + node: undefined, + + setNode: action((state, payload) => { + state.node = payload; + }), +}); + +const NodeRouter = () => { + const params = useParams<'id'>(); + + const { clearFlashes, clearAndAddHttpError } = useStoreActions( + (actions: Actions) => actions.flashes, + ); + const [loading, setLoading] = useState(true); + + const node = Context.useStoreState(state => state.node); + const setNode = Context.useStoreActions(actions => actions.setNode); + + useEffect(() => { + clearFlashes('node'); + + getNode(Number(params.id), ['database_host', 'location']) + .then(node => setNode(node)) + .catch(error => { + console.error(error); + clearAndAddHttpError({ key: 'node', error }); + }) + .then(() => setLoading(false)); + }, []); + + if (loading || node === undefined) { + return ( + + + +
    + +
    +
    + ); + } + + return ( + +
    +
    +

    {node.name}

    +

    + {node.uuid} +

    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + } /> + } /> + } /> + } /> + } /> + +
    + ); +}; + +export default () => { + return ( + + + + ); +}; diff --git a/resources/scripts/components/admin/nodes/NodeServers.tsx b/resources/scripts/components/admin/nodes/NodeServers.tsx new file mode 100644 index 0000000000..7b11274ddf --- /dev/null +++ b/resources/scripts/components/admin/nodes/NodeServers.tsx @@ -0,0 +1,10 @@ +import { Context } from '@/components/admin/nodes/NodeRouter'; +import ServersTable from '@/components/admin/servers/ServersTable'; + +function NodeServers() { + const node = Context.useStoreState(state => state.node); + + return ; +} + +export default NodeServers; diff --git a/resources/scripts/components/admin/nodes/NodeSettingsContainer.tsx b/resources/scripts/components/admin/nodes/NodeSettingsContainer.tsx new file mode 100644 index 0000000000..6dfa443c5d --- /dev/null +++ b/resources/scripts/components/admin/nodes/NodeSettingsContainer.tsx @@ -0,0 +1,95 @@ +import { faDatabase } from '@fortawesome/free-solid-svg-icons'; +import { Field as FormikField, useFormikContext } from 'formik'; +import tw from 'twin.macro'; + +import type { Node } from '@/api/admin/nodes/getNodes'; +import AdminBox from '@/components/admin/AdminBox'; +import DatabaseSelect from '@/components/admin/nodes/DatabaseSelect'; +import LocationSelect from '@/components/admin/nodes/LocationSelect'; +import Label from '@/components/elements/Label'; +import Field from '@/components/elements/Field'; +import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; + +export default function NodeSettingsContainer({ node }: { node?: Node }) { + const { isSubmitting } = useFormikContext(); + + return ( + + + +
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    + + +
    + + + +
    +
    + +
    + + +
    + + + +
    +
    + +
    + + +
    + + + +
    +
    +
    + ); +} diff --git a/resources/scripts/components/admin/nodes/NodesContainer.tsx b/resources/scripts/components/admin/nodes/NodesContainer.tsx new file mode 100644 index 0000000000..2ad97ad7fd --- /dev/null +++ b/resources/scripts/components/admin/nodes/NodesContainer.tsx @@ -0,0 +1,271 @@ +import type { ChangeEvent } from 'react'; +import { useContext, useEffect } from 'react'; +import type { Filters } from '@/api/admin/servers/getServers'; +import getNodes, { Context as NodesContext } from '@/api/admin/nodes/getNodes'; +import FlashMessageRender from '@/components/FlashMessageRender'; +import useFlash from '@/plugins/useFlash'; +import { AdminContext } from '@/state/admin'; +import { NavLink } from 'react-router-dom'; +import tw from 'twin.macro'; +import AdminContentBlock from '@/components/admin/AdminContentBlock'; +import AdminCheckbox from '@/components/admin/AdminCheckbox'; +import AdminTable, { + TableBody, + TableHead, + TableHeader, + TableRow, + Pagination, + Loading, + NoItems, + ContentWrapper, + useTableHooks, +} from '@/components/admin/AdminTable'; +import Button from '@/components/elements/Button'; +import CopyOnClick from '@/components/elements/CopyOnClick'; +import { bytesToString, mbToBytes } from '@/lib/formatters'; + +const RowCheckbox = ({ id }: { id: number }) => { + const isChecked = AdminContext.useStoreState(state => state.nodes.selectedNodes.indexOf(id) >= 0); + const appendSelectedNode = AdminContext.useStoreActions(actions => actions.nodes.appendSelectedNode); + const removeSelectedNode = AdminContext.useStoreActions(actions => actions.nodes.removeSelectedNode); + + return ( + ) => { + if (e.currentTarget.checked) { + appendSelectedNode(id); + } else { + removeSelectedNode(id); + } + }} + /> + ); +}; + +const NodesContainer = () => { + const { page, setPage, setFilters, sort, setSort, sortDirection } = useContext(NodesContext); + const { clearFlashes, clearAndAddHttpError } = useFlash(); + const { data: nodes, error, isValidating } = getNodes(['location']); + + useEffect(() => { + if (!error) { + clearFlashes('nodes'); + return; + } + + clearAndAddHttpError({ key: 'nodes', error }); + }, [error]); + + const length = nodes?.items?.length || 0; + + const setSelectedNodes = AdminContext.useStoreActions(actions => actions.nodes.setSelectedNodes); + const selectedNodesLength = AdminContext.useStoreState(state => state.nodes.selectedNodes.length); + + const onSelectAllClick = (e: ChangeEvent) => { + setSelectedNodes(e.currentTarget.checked ? nodes?.items?.map(node => node.id) || [] : []); + }; + + const onSearch = (query: string): Promise => { + return new Promise(resolve => { + if (query.length < 2) { + setFilters(null); + } else { + setFilters({ name: query }); + } + return resolve(); + }); + }; + + useEffect(() => { + setSelectedNodes([]); + }, [page]); + + return ( + +
    +
    +

    Nodes

    +

    + All nodes available on the system. +

    +
    + +
    + + + +
    +
    + + + + + + +
    + + + setSort('id')} + /> + setSort('name')} + /> + setSort('location_id')} + /> + setSort('fqdn')} + /> + setSort('memory')} + /> + setSort('disk')} + /> + + + + + + {nodes !== undefined && + !error && + !isValidating && + length > 0 && + nodes.items.map(node => ( + + + + + + + + {/* TODO: Have permission check for displaying location information. */} + + + + + + + + + + + + ))} + +
    + + + + + {node.id} + + + + + {node.name} + + + +
    + {node.relations.location?.short} +
    + +
    + {node.relations.location?.long} +
    +
    +
    + + + {node.fqdn} + + + + {bytesToString(mbToBytes(node.memory))} + + {bytesToString(mbToBytes(node.disk))} + + {node.scheme === 'https' ? ( + + Secure + + ) : ( + + Non-Secure + + )} + + {/* TODO: Change color based off of online/offline status */} + + + +
    + + {nodes === undefined || (error && isValidating) ? ( + + ) : length < 1 ? ( + + ) : null} +
    +
    +
    +
    +
    + ); +}; + +export default () => { + const hooks = useTableHooks(); + + return ( + + + + ); +}; diff --git a/resources/scripts/components/admin/nodes/allocations/AllocationTable.tsx b/resources/scripts/components/admin/nodes/allocations/AllocationTable.tsx new file mode 100644 index 0000000000..6ada3e9ec7 --- /dev/null +++ b/resources/scripts/components/admin/nodes/allocations/AllocationTable.tsx @@ -0,0 +1,216 @@ +import type { ChangeEvent } from 'react'; +import { useContext, useEffect } from 'react'; +import { NavLink } from 'react-router-dom'; +import tw from 'twin.macro'; + +import type { Filters } from '@/api/admin/nodes/allocations/getAllocations'; +import getAllocations, { Context as AllocationsContext } from '@/api/admin/nodes/allocations/getAllocations'; +import AdminCheckbox from '@/components/admin/AdminCheckbox'; +import AdminTable, { + ContentWrapper, + Loading, + NoItems, + Pagination, + TableBody, + TableHead, + TableHeader, + useTableHooks, +} from '@/components/admin/AdminTable'; +import DeleteAllocationButton from '@/components/admin/nodes/allocations/DeleteAllocationButton'; +import CopyOnClick from '@/components/elements/CopyOnClick'; +import useFlash from '@/plugins/useFlash'; +import { AdminContext } from '@/state/admin'; + +function RowCheckbox({ id }: { id: number }) { + const isChecked = AdminContext.useStoreState(state => state.allocations.selectedAllocations.indexOf(id) >= 0); + const appendSelectedAllocation = AdminContext.useStoreActions( + actions => actions.allocations.appendSelectedAllocation, + ); + const removeSelectedAllocation = AdminContext.useStoreActions( + actions => actions.allocations.removeSelectedAllocation, + ); + + return ( + ) => { + if (e.currentTarget.checked) { + appendSelectedAllocation(id); + } else { + removeSelectedAllocation(id); + } + }} + /> + ); +} + +interface Props { + nodeId: number; + filters?: Filters; +} + +function AllocationsTable({ nodeId, filters }: Props) { + const { clearFlashes, clearAndAddHttpError } = useFlash(); + + const { page, setPage, setFilters, sort, setSort, sortDirection } = useContext(AllocationsContext); + const { data: allocations, error, isValidating, mutate } = getAllocations(nodeId, ['server']); + + const length = allocations?.items?.length || 0; + + const setSelectedAllocations = AdminContext.useStoreActions(actions => actions.allocations.setSelectedAllocations); + const selectedAllocationLength = AdminContext.useStoreState(state => state.allocations.selectedAllocations.length); + + const onSelectAllClick = (e: ChangeEvent) => { + setSelectedAllocations( + e.currentTarget.checked ? allocations?.items?.map?.(allocation => allocation.id) || [] : [], + ); + }; + + const onSearch = (query: string): Promise => { + return new Promise(resolve => { + if (query.length < 2) { + setFilters(filters || null); + } else { + setFilters({ ...filters, ip: query }); + } + return resolve(); + }); + }; + + useEffect(() => { + setSelectedAllocations([]); + }, [page]); + + useEffect(() => { + if (!error) { + clearFlashes('allocations'); + return; + } + + clearAndAddHttpError({ key: 'allocations', error }); + }, [error]); + + return ( + + + +
    + + + setSort('ip')} + /> + + setSort('port')} + /> + + + + + + {allocations !== undefined && + !error && + !isValidating && + length > 0 && + allocations.items.map(allocation => ( + + + + + + {allocation.alias !== null ? ( + + ) : ( + + + {allocation.relations.server !== undefined ? ( + + ) : ( + + + ))} + +
    + + + + + {allocation.ip} + + + + + + {allocation.alias} + + + + )} + + + + + {allocation.port} + + + + + {allocation.relations.server.name} + + + )} + + + { + await mutate(allocations => ({ + pagination: allocations!.pagination, + items: allocations!.items.filter( + a => a.id === allocation.id, + ), + })); + + // Go back a page if no more items will exist on the current page. + if (allocations?.items.length - (1 % 10) === 0) { + setPage(p => p - 1); + } + }} + /> +
    + + {allocations === undefined || (error && isValidating) ? ( + + ) : length < 1 ? ( + + ) : null} +
    +
    +
    +
    + ); +} + +export default (props: Props) => { + const hooks = useTableHooks(props.filters); + + return ( + + + + ); +}; diff --git a/resources/scripts/components/admin/nodes/allocations/CreateAllocationForm.tsx b/resources/scripts/components/admin/nodes/allocations/CreateAllocationForm.tsx new file mode 100644 index 0000000000..4c2df4ba64 --- /dev/null +++ b/resources/scripts/components/admin/nodes/allocations/CreateAllocationForm.tsx @@ -0,0 +1,118 @@ +import type { FormikHelpers } from 'formik'; +import { Form, Formik } from 'formik'; +import { useEffect, useState } from 'react'; +import tw from 'twin.macro'; +import { array, number, object, string } from 'yup'; + +import createAllocation from '@/api/admin/nodes/allocations/createAllocation'; +import getAllocations from '@/api/admin/nodes/getAllocations'; +import getAllocations2 from '@/api/admin/nodes/allocations/getAllocations'; +import Button from '@/components/elements/Button'; +import Field from '@/components/elements/Field'; +import type { Option } from '@/components/elements/SelectField'; +import SelectField from '@/components/elements/SelectField'; + +interface Values { + ips: string[]; + ports: number[]; + alias: string; +} + +const distinct = (value: any, index: any, self: any) => { + return self.indexOf(value) === index; +}; + +function CreateAllocationForm({ nodeId }: { nodeId: number }) { + const [ips, setIPs] = useState([]); + const [ports] = useState([]); + + const { mutate } = getAllocations2(nodeId, ['server']); + + useEffect(() => { + getAllocations(nodeId).then(allocations => { + setIPs( + allocations + .map(a => a.ip) + .filter(distinct) + .map(ip => { + return { value: ip, label: ip }; + }), + ); + }); + }, [nodeId]); + + const isValidIP = (inputValue: string): boolean => { + // TODO: Better way of checking for a valid ip (and CIDR) + return inputValue.match(/^([0-9a-f.:/]+)$/) !== null; + }; + + const isValidPort = (inputValue: string): boolean => { + // TODO: Better way of checking for a valid port (and port range) + return inputValue.match(/^([0-9-]+)$/) !== null; + }; + + const submit = ({ ips, ports, alias }: Values, { setSubmitting }: FormikHelpers) => { + setSubmitting(false); + + ips.forEach(async ip => { + const allocations = await createAllocation(nodeId, { ip, ports, alias }, ['server']); + await mutate(data => ({ ...data!, items: { ...data!.items!, ...allocations } })); + }); + }; + + return ( + + {({ isSubmitting, isValid }) => ( +
    + + + + +
    + +
    + +
    +
    + +
    +
    + + )} +
    + ); +} + +export default CreateAllocationForm; diff --git a/resources/scripts/components/admin/nodes/allocations/DeleteAllocationButton.tsx b/resources/scripts/components/admin/nodes/allocations/DeleteAllocationButton.tsx new file mode 100644 index 0000000000..2096303fda --- /dev/null +++ b/resources/scripts/components/admin/nodes/allocations/DeleteAllocationButton.tsx @@ -0,0 +1,77 @@ +import type { Actions } from 'easy-peasy'; +import { useStoreActions } from 'easy-peasy'; +import { useState } from 'react'; +import tw from 'twin.macro'; + +import deleteAllocation from '@/api/admin/nodes/allocations/deleteAllocation'; +import Button from '@/components/elements/Button'; +import ConfirmationModal from '@/components/elements/ConfirmationModal'; +import type { ApplicationStore } from '@/state'; + +interface Props { + nodeId: number; + allocationId: number; + onDeleted?: () => void; +} + +export default ({ nodeId, allocationId, onDeleted }: Props) => { + const [visible, setVisible] = useState(false); + const [loading, setLoading] = useState(false); + + const { clearFlashes, clearAndAddHttpError } = useStoreActions( + (actions: Actions) => actions.flashes, + ); + + const onDelete = () => { + setLoading(true); + clearFlashes('allocation'); + + deleteAllocation(nodeId, allocationId) + .then(() => { + setLoading(false); + setVisible(false); + if (onDeleted !== undefined) { + onDeleted(); + } + }) + .catch(error => { + console.error(error); + clearAndAddHttpError({ key: 'allocation', error }); + + setLoading(false); + setVisible(false); + }); + }; + + return ( + <> + setVisible(false)} + > + Are you sure you want to delete this allocation? + + + + + ); +}; diff --git a/resources/scripts/components/admin/overview/OverviewContainer.tsx b/resources/scripts/components/admin/overview/OverviewContainer.tsx new file mode 100644 index 0000000000..92850a0f5c --- /dev/null +++ b/resources/scripts/components/admin/overview/OverviewContainer.tsx @@ -0,0 +1,103 @@ +import type { ReactNode } from 'react'; +import { useEffect, useState } from 'react'; +import tw from 'twin.macro'; + +import type { VersionData } from '@/api/admin/getVersion'; +import getVersion from '@/api/admin/getVersion'; +import AdminContentBlock from '@/components/admin/AdminContentBlock'; +import FlashMessageRender from '@/components/FlashMessageRender'; +import Spinner from '@/components/elements/Spinner'; +import useFlash from '@/plugins/useFlash'; + +const Code = ({ children }: { children: ReactNode }) => { + return ( + + {children} + + ); +}; + +export default () => { + const { clearFlashes, clearAndAddHttpError } = useFlash(); + const [loading, setLoading] = useState(true); + const [versionData, setVersionData] = useState(undefined); + + useEffect(() => { + clearFlashes('overview'); + + getVersion() + .then(versionData => setVersionData(versionData)) + .catch(error => { + console.error(error); + clearAndAddHttpError({ key: 'overview', error }); + }) + .then(() => setLoading(false)); + }, []); + + return ( + +
    +
    +

    Overview

    +

    + A quick glance at your system. +

    +
    +
    + + + +
    + {loading ? ( +
    + +
    + ) : ( +
    +
    +

    + + + + System Information +

    +
    + +
    + {versionData?.panel.current === 'canary' ? ( +

    + I hope you enjoy living on the edge because you are running a{' '} + {versionData?.panel.current} version of Pterodactyl. +

    + ) : versionData?.panel.latest === versionData?.panel.current ? ( +

    + Your panel is up-to-date. The latest version + is {versionData?.panel.latest} and you are running version{' '} + {versionData?.panel.current}. +

    + ) : ( +

    + Your panel is not up-to-date. The latest + version is {versionData?.panel.latest} and you are running version{' '} + {versionData?.panel.current}. +

    + )} +
    +
    + )} +
    +
    + ); +}; diff --git a/resources/scripts/components/admin/roles/NewRoleButton.tsx b/resources/scripts/components/admin/roles/NewRoleButton.tsx new file mode 100644 index 0000000000..210b84492c --- /dev/null +++ b/resources/scripts/components/admin/roles/NewRoleButton.tsx @@ -0,0 +1,107 @@ +import type { FormikHelpers } from 'formik'; +import { Form, Formik } from 'formik'; +import { useState } from 'react'; +import tw from 'twin.macro'; +import { object, string } from 'yup'; + +import { getRoles, createRole } from '@/api/admin/roles'; +import FlashMessageRender from '@/components/FlashMessageRender'; +import Button from '@/components/elements/Button'; +import Field from '@/components/elements/Field'; +import Modal from '@/components/elements/Modal'; +import useFlash from '@/plugins/useFlash'; + +interface Values { + name: string; + description: string; +} + +const schema = object().shape({ + name: string().required('A role name must be provided.').max(32, 'Role name must not exceed 32 characters.'), + description: string().max(255, 'Role description must not exceed 255 characters.'), +}); + +export default () => { + const [visible, setVisible] = useState(false); + const { clearFlashes, clearAndAddHttpError } = useFlash(); + const { mutate } = getRoles(); + + const submit = ({ name, description }: Values, { setSubmitting }: FormikHelpers) => { + clearFlashes('role:create'); + setSubmitting(true); + + createRole(name, description) + .then(async role => { + await mutate(data => ({ ...data!, items: data!.items.concat(role) }), false); + setVisible(false); + }) + .catch(error => { + clearAndAddHttpError({ key: 'role:create', error }); + setSubmitting(false); + }); + }; + + return ( + <> + + {({ isSubmitting, resetForm }) => ( + { + resetForm(); + setVisible(false); + }} + > + +

    New Role

    +
    + + +
    + +
    + +
    + + +
    + +
    + )} +
    + + + + ); +}; diff --git a/resources/scripts/components/admin/roles/RoleDeleteButton.tsx b/resources/scripts/components/admin/roles/RoleDeleteButton.tsx new file mode 100644 index 0000000000..e312823c8b --- /dev/null +++ b/resources/scripts/components/admin/roles/RoleDeleteButton.tsx @@ -0,0 +1,73 @@ +import type { Actions } from 'easy-peasy'; +import { useStoreActions } from 'easy-peasy'; +import { useState } from 'react'; +import tw from 'twin.macro'; + +import { deleteRole } from '@/api/admin/roles'; +import Button from '@/components/elements/Button'; +import ConfirmationModal from '@/components/elements/ConfirmationModal'; +import type { ApplicationStore } from '@/state'; + +interface Props { + roleId: number; + onDeleted: () => void; +} + +export default ({ roleId, onDeleted }: Props) => { + const [visible, setVisible] = useState(false); + const [loading, setLoading] = useState(false); + + const { clearFlashes, clearAndAddHttpError } = useStoreActions( + (actions: Actions) => actions.flashes, + ); + + const onDelete = () => { + setLoading(true); + clearFlashes('role'); + + deleteRole(roleId) + .then(() => { + setLoading(false); + onDeleted(); + }) + .catch(error => { + console.error(error); + clearAndAddHttpError({ key: 'role', error }); + + setLoading(false); + setVisible(false); + }); + }; + + return ( + <> + setVisible(false)} + > + Are you sure you want to delete this role? + + + + + ); +}; diff --git a/resources/scripts/components/admin/roles/RoleEditContainer.tsx b/resources/scripts/components/admin/roles/RoleEditContainer.tsx new file mode 100644 index 0000000000..dc3a135d21 --- /dev/null +++ b/resources/scripts/components/admin/roles/RoleEditContainer.tsx @@ -0,0 +1,176 @@ +import type { Action, Actions } from 'easy-peasy'; +import { action, createContextStore, useStoreActions } from 'easy-peasy'; +import type { FormikHelpers } from 'formik'; +import { Form, Formik } from 'formik'; +import { useEffect, useState } from 'react'; +import { useNavigate, useParams } from 'react-router-dom'; +import tw from 'twin.macro'; +import { object, string } from 'yup'; + +import { getRole, updateRole } from '@/api/admin/roles'; +import FlashMessageRender from '@/components/FlashMessageRender'; +import AdminBox from '@/components/admin/AdminBox'; +import AdminContentBlock from '@/components/admin/AdminContentBlock'; +import RoleDeleteButton from '@/components/admin/roles/RoleDeleteButton'; +import Button from '@/components/elements/Button'; +import Field from '@/components/elements/Field'; +import Spinner from '@/components/elements/Spinner'; +import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; +import type { UserRole } from '@definitions/admin'; +import type { ApplicationStore } from '@/state'; + +interface ctx { + role: UserRole | undefined; + setRole: Action; +} + +export const Context = createContextStore({ + role: undefined, + + setRole: action((state, payload) => { + state.role = payload; + }), +}); + +interface Values { + name: string; + description: string; +} + +const EditInformationContainer = () => { + const navigate = useNavigate(); + + const { clearFlashes, clearAndAddHttpError } = useStoreActions( + (actions: Actions) => actions.flashes, + ); + + const role = Context.useStoreState(state => state.role); + const setRole = Context.useStoreActions(actions => actions.setRole); + + if (role === undefined) { + return <>; + } + + const submit = ({ name, description }: Values, { setSubmitting }: FormikHelpers) => { + clearFlashes('role'); + + updateRole(role.id, name, description) + .then(() => setRole({ ...role, name, description })) + .catch(error => { + console.error(error); + clearAndAddHttpError({ key: 'role', error }); + }) + .then(() => setSubmitting(false)); + }; + + return ( + + {({ isSubmitting, isValid }) => ( + <> + + + +
    +
    + +
    + +
    + +
    + +
    +
    + navigate('/admin/roles')} /> +
    + +
    + +
    +
    +
    +
    + + )} +
    + ); +}; + +const RoleEditContainer = () => { + const params = useParams<'id'>(); + + const { clearFlashes, clearAndAddHttpError } = useStoreActions( + (actions: Actions) => actions.flashes, + ); + const [loading, setLoading] = useState(true); + + const role = Context.useStoreState(state => state.role); + const setRole = Context.useStoreActions(actions => actions.setRole); + + useEffect(() => { + clearFlashes('role'); + + getRole(Number(params.id)) + .then(role => setRole(role)) + .catch(error => { + console.error(error); + clearAndAddHttpError({ key: 'role', error }); + }) + .then(() => setLoading(false)); + }, []); + + if (loading || role === undefined) { + return ( + + + +
    + +
    +
    + ); + } + + return ( + +
    +
    +

    {role.name}

    + {(role.description || '').length < 1 ? ( +

    + No description +

    + ) : ( +

    + {role.description} +

    + )} +
    +
    + + + + +
    + ); +}; + +export default () => { + return ( + + + + ); +}; diff --git a/resources/scripts/components/admin/roles/RolesContainer.tsx b/resources/scripts/components/admin/roles/RolesContainer.tsx new file mode 100644 index 0000000000..7563edd67f --- /dev/null +++ b/resources/scripts/components/admin/roles/RolesContainer.tsx @@ -0,0 +1,182 @@ +import type { ChangeEvent } from 'react'; +import { useContext, useEffect } from 'react'; +import { NavLink } from 'react-router-dom'; +import tw from 'twin.macro'; + +import type { Filters } from '@/api/admin/roles'; +import { getRoles, Context as RolesContext } from '@/api/admin/roles'; +import { AdminContext } from '@/state/admin'; +import NewRoleButton from '@/components/admin/roles/NewRoleButton'; +import FlashMessageRender from '@/components/FlashMessageRender'; +import AdminContentBlock from '@/components/admin/AdminContentBlock'; +import AdminCheckbox from '@/components/admin/AdminCheckbox'; +import AdminTable, { + TableBody, + TableHead, + TableHeader, + TableRow, + Pagination, + Loading, + NoItems, + ContentWrapper, + useTableHooks, +} from '@/components/admin/AdminTable'; +import CopyOnClick from '@/components/elements/CopyOnClick'; +import useFlash from '@/plugins/useFlash'; + +const RowCheckbox = ({ id }: { id: number }) => { + const isChecked = AdminContext.useStoreState(state => state.roles.selectedRoles.indexOf(id) >= 0); + const appendSelectedRole = AdminContext.useStoreActions(actions => actions.roles.appendSelectedRole); + const removeSelectedRole = AdminContext.useStoreActions(actions => actions.roles.removeSelectedRole); + + return ( + ) => { + if (e.currentTarget.checked) { + appendSelectedRole(id); + } else { + removeSelectedRole(id); + } + }} + /> + ); +}; + +const RolesContainer = () => { + const { page, setPage, setFilters, sort, setSort, sortDirection } = useContext(RolesContext); + const { clearFlashes, clearAndAddHttpError } = useFlash(); + const { data: roles, error, isValidating } = getRoles(); + + useEffect(() => { + if (!error) { + clearFlashes('roles'); + return; + } + + clearAndAddHttpError({ key: 'roles', error }); + }, [error]); + + const length = roles?.items?.length || 0; + + const setSelectedRoles = AdminContext.useStoreActions(actions => actions.roles.setSelectedRoles); + const selectedRolesLength = AdminContext.useStoreState(state => state.roles.selectedRoles.length); + + const onSelectAllClick = (e: ChangeEvent) => { + setSelectedRoles(e.currentTarget.checked ? roles?.items?.map(role => role.id) || [] : []); + }; + + const onSearch = (query: string): Promise => { + return new Promise(resolve => { + if (query.length < 2) { + setFilters(null); + } else { + setFilters({ name: query }); + } + return resolve(); + }); + }; + + useEffect(() => { + setSelectedRoles([]); + }, [page]); + + return ( + +
    +
    +

    Roles

    +

    + Soon™ +

    +
    + +
    + +
    +
    + + + + + + +
    + + + setSort('id')} + /> + setSort('name')} + /> + + + + + {roles !== undefined && + !error && + !isValidating && + length > 0 && + roles.items.map(role => ( + + + + + + + + + + ))} + +
    + + + + + {role.id} + + + + + {role.name} + + + {role.description} +
    + + {roles === undefined || (error && isValidating) ? ( + + ) : length < 1 ? ( + + ) : null} +
    +
    +
    +
    +
    + ); +}; + +export default () => { + const hooks = useTableHooks(); + + return ( + + + + ); +}; diff --git a/resources/scripts/components/admin/servers/EggSelect.tsx b/resources/scripts/components/admin/servers/EggSelect.tsx new file mode 100644 index 0000000000..c6b7882746 --- /dev/null +++ b/resources/scripts/components/admin/servers/EggSelect.tsx @@ -0,0 +1,75 @@ +import { useField } from 'formik'; +import type { ChangeEvent } from 'react'; +import { useEffect, useState } from 'react'; + +import type { WithRelationships } from '@/api/admin'; +import type { Egg } from '@/api/admin/egg'; +import { searchEggs } from '@/api/admin/egg'; +import Label from '@/components/elements/Label'; +import Select from '@/components/elements/Select'; + +interface Props { + nestId?: number; + selectedEggId?: number; + onEggSelect: (egg: Egg | null) => void; +} + +export default ({ nestId, selectedEggId, onEggSelect }: Props) => { + const [, , { setValue, setTouched }] = useField>('environment'); + const [eggs, setEggs] = useState[] | null>(null); + + const selectEgg = (egg: Egg | null) => { + if (egg === null) { + onEggSelect(null); + return; + } + + // Clear values + setValue({}); + setTouched(true); + + onEggSelect(egg); + + const values: Record = {}; + egg.relationships.variables?.forEach(v => { + values[v.environmentVariable] = v.defaultValue; + }); + setValue(values); + setTouched(true); + }; + + useEffect(() => { + if (!nestId) { + setEggs(null); + return; + } + + searchEggs(nestId, {}) + .then(eggs => { + setEggs(eggs); + selectEgg(eggs[0] || null); + }) + .catch(error => console.error(error)); + }, [nestId]); + + const onSelectChange = (e: ChangeEvent) => { + selectEgg(eggs?.find(egg => egg.id.toString() === e.currentTarget.value) || null); + }; + + return ( + <> + + + + ); +}; diff --git a/resources/scripts/components/admin/servers/NestSelector.tsx b/resources/scripts/components/admin/servers/NestSelector.tsx new file mode 100644 index 0000000000..762bf39a58 --- /dev/null +++ b/resources/scripts/components/admin/servers/NestSelector.tsx @@ -0,0 +1,44 @@ +import { useEffect, useState } from 'react'; + +import type { Nest } from '@/api/admin/nest'; +import { searchNests } from '@/api/admin/nest'; +import Label from '@/components/elements/Label'; +import Select from '@/components/elements/Select'; + +interface Props { + selectedNestId?: number; + onNestSelect: (nest: number) => void; +} + +export default ({ selectedNestId, onNestSelect }: Props) => { + const [nests, setNests] = useState(null); + + useEffect(() => { + searchNests({}) + .then(nests => { + setNests(nests); + if (selectedNestId === 0 && nests.length > 0) { + // @ts-expect-error go away + onNestSelect(nests[0].id); + } + }) + .catch(error => console.error(error)); + }, []); + + return ( + <> + + + + ); +}; diff --git a/resources/scripts/components/admin/servers/NewServerContainer.tsx b/resources/scripts/components/admin/servers/NewServerContainer.tsx new file mode 100644 index 0000000000..1970d48061 --- /dev/null +++ b/resources/scripts/components/admin/servers/NewServerContainer.tsx @@ -0,0 +1,225 @@ +import { faNetworkWired } from '@fortawesome/free-solid-svg-icons'; +import type { FormikHelpers } from 'formik'; +import { Form, Formik, useFormikContext } from 'formik'; +import { useEffect, useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import tw from 'twin.macro'; +import { object } from 'yup'; + +import type { Egg } from '@/api/admin/egg'; +import type { CreateServerRequest } from '@/api/admin/servers/createServer'; +import createServer from '@/api/admin/servers/createServer'; +import type { Allocation, Node } from '@/api/admin/node'; +import { getAllocations } from '@/api/admin/node'; +import AdminBox from '@/components/admin/AdminBox'; +import NodeSelect from '@/components/admin/servers/NodeSelect'; +import { + ServerImageContainer, + ServerServiceContainer, + ServerVariableContainer, +} from '@/components/admin/servers/ServerStartupContainer'; +import BaseSettingsBox from '@/components/admin/servers/settings/BaseSettingsBox'; +import FeatureLimitsBox from '@/components/admin/servers/settings/FeatureLimitsBox'; +import ServerResourceBox from '@/components/admin/servers/settings/ServerResourceBox'; +import Button from '@/components/elements/Button'; +import Field from '@/components/elements/Field'; +import FormikSwitch from '@/components/elements/FormikSwitch'; +import Label from '@/components/elements/Label'; +import Select from '@/components/elements/Select'; +import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; +import FlashMessageRender from '@/components/FlashMessageRender'; +import useFlash from '@/plugins/useFlash'; +import AdminContentBlock from '@/components/admin/AdminContentBlock'; + +function InternalForm() { + const { + isSubmitting, + isValid, + setFieldValue, + values: { environment }, + } = useFormikContext(); + + const [egg, setEgg] = useState(null); + const [node, setNode] = useState(null); + const [allocations, setAllocations] = useState(null); + + useEffect(() => { + if (egg === null) { + return; + } + + setFieldValue('eggId', egg.id); + setFieldValue('startup', ''); + setFieldValue('image', egg.dockerImages.length > 0 ? egg.dockerImages[0] : ''); + }, [egg]); + + useEffect(() => { + if (node === null) { + return; + } + + // server_id: 0 filters out assigned allocations + getAllocations(node.id, { filters: { server_id: '0' } }).then(setAllocations); + }, [node]); + + return ( +
    +
    +
    + + +
    + +
    +
    + + +
    +
    + +
    +
    + + +
    + {/*
    */} + {/* /!* TODO: Multi-select *!/*/} + {/* */} + {/* */} + {/*
    */} +
    +
    + + +
    + + + + + + + +
    + {/* This ensures that no variables are rendered unless the environment has a value for the variable. */} + {egg?.relationships.variables + ?.filter(v => Object.keys(environment).find(e => e === v.environmentVariable) !== undefined) + .map((v, i) => ( + + ))} +
    + +
    +
    + +
    +
    +
    +
    + ); +} + +export default () => { + const navigate = useNavigate(); + + const { clearFlashes, clearAndAddHttpError } = useFlash(); + + const submit = (r: CreateServerRequest, { setSubmitting }: FormikHelpers) => { + clearFlashes('server:create'); + + createServer(r) + .then(s => navigate(`/admin/servers/${s.id}`)) + .catch(error => clearAndAddHttpError({ key: 'server:create', error })) + .then(() => setSubmitting(false)); + }; + + return ( + +
    +
    +

    New Server

    +

    + Add a new server to the panel. +

    +
    +
    + + + + + + +
    + ); +}; diff --git a/resources/scripts/components/admin/servers/NodeSelect.tsx b/resources/scripts/components/admin/servers/NodeSelect.tsx new file mode 100644 index 0000000000..bd1e3a6759 --- /dev/null +++ b/resources/scripts/components/admin/servers/NodeSelect.tsx @@ -0,0 +1,46 @@ +import { useFormikContext } from 'formik'; +import { useState } from 'react'; + +import type { Node } from '@/api/admin/node'; +import { searchNodes } from '@/api/admin/node'; +import SearchableSelect, { Option } from '@/components/elements/SearchableSelect'; + +export default ({ node, setNode }: { node: Node | null; setNode: (_: Node | null) => void }) => { + const { setFieldValue } = useFormikContext(); + + const [nodes, setNodes] = useState(null); + + const onSearch = async (query: string) => { + setNodes(await searchNodes({ filters: { name: query } })); + }; + + const onSelect = (node: Node | null) => { + setNode(node); + setFieldValue('nodeId', node?.id || null); + }; + + const getSelectedText = (node: Node | null): string => node?.name || ''; + + return ( + + {nodes?.map(d => ( + + ))} + + ); +}; diff --git a/resources/scripts/components/admin/servers/OwnerSelect.tsx b/resources/scripts/components/admin/servers/OwnerSelect.tsx new file mode 100644 index 0000000000..25de1133a3 --- /dev/null +++ b/resources/scripts/components/admin/servers/OwnerSelect.tsx @@ -0,0 +1,47 @@ +import { useFormikContext } from 'formik'; +import { useState } from 'react'; + +import { searchUserAccounts } from '@/api/admin/users'; +import SearchableSelect, { Option } from '@/components/elements/SearchableSelect'; +import type { User } from '@definitions/admin'; + +export default ({ selected }: { selected?: User }) => { + const { setFieldValue } = useFormikContext(); + + const [user, setUser] = useState(selected || null); + const [users, setUsers] = useState(null); + + const onSearch = async (query: string) => { + setUsers(await searchUserAccounts({ filters: { username: query, email: query } })); + }; + + const onSelect = (user: User | null) => { + setUser(user); + setFieldValue('ownerId', user?.id || null); + }; + + const getSelectedText = (user: User | null): string => user?.email || ''; + + return ( + + {users?.map(d => ( + + ))} + + ); +}; diff --git a/resources/scripts/components/admin/servers/ServerDeleteButton.tsx b/resources/scripts/components/admin/servers/ServerDeleteButton.tsx new file mode 100644 index 0000000000..27acf2e2c7 --- /dev/null +++ b/resources/scripts/components/admin/servers/ServerDeleteButton.tsx @@ -0,0 +1,66 @@ +import { TrashIcon } from '@heroicons/react/outline'; +import type { Actions } from 'easy-peasy'; +import { useStoreActions } from 'easy-peasy'; +import { useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import tw from 'twin.macro'; + +import Button from '@/components/elements/Button'; +import ConfirmationModal from '@/components/elements/ConfirmationModal'; +import deleteServer from '@/api/admin/servers/deleteServer'; +import { useServerFromRoute } from '@/api/admin/server'; +import type { ApplicationStore } from '@/state'; + +export default () => { + const navigate = useNavigate(); + const [visible, setVisible] = useState(false); + const [loading, setLoading] = useState(false); + const { data: server } = useServerFromRoute(); + + const { clearFlashes, clearAndAddHttpError } = useStoreActions( + (actions: Actions) => actions.flashes, + ); + + const onDelete = () => { + if (!server) return; + + setLoading(true); + clearFlashes('server'); + + deleteServer(server.id) + .then(() => navigate('/admin/servers')) + .catch(error => { + console.error(error); + clearAndAddHttpError({ key: 'server', error }); + + setLoading(false); + setVisible(false); + }); + }; + + if (!server) return null; + + return ( + <> + setVisible(false)} + > + Are you sure you want to delete this server? + + + + ); +}; diff --git a/resources/scripts/components/admin/servers/ServerManageContainer.tsx b/resources/scripts/components/admin/servers/ServerManageContainer.tsx new file mode 100644 index 0000000000..1fda0d5de1 --- /dev/null +++ b/resources/scripts/components/admin/servers/ServerManageContainer.tsx @@ -0,0 +1,60 @@ +import tw from 'twin.macro'; + +import { useServerFromRoute } from '@/api/admin/server'; +import AdminBox from '@/components/admin/AdminBox'; +import Button from '@/components/elements/Button'; + +export default () => { + const { data: server } = useServerFromRoute(); + + if (!server) return null; + + return ( +
    +
    + +
    +
    + + + +
    +

    Danger! This could overwrite server data.

    +
    + +

    + This will reinstall the server with the assigned service scripts. +

    +
    +
    +
    + + +

    + If you need to change the install status from uninstalled to installed, or vice versa, you may + do so with the button below. +

    +
    +
    +
    + + +

    + This will suspend the server, stop any running processes, and immediately block the user from + being able to access their files or otherwise manage the server through the panel or API. +

    +
    +
    +
    + ); +}; diff --git a/resources/scripts/components/admin/servers/ServerRouter.tsx b/resources/scripts/components/admin/servers/ServerRouter.tsx new file mode 100644 index 0000000000..f21fc37953 --- /dev/null +++ b/resources/scripts/components/admin/servers/ServerRouter.tsx @@ -0,0 +1,76 @@ +import { useEffect } from 'react'; +import { Route, Routes, useParams } from 'react-router-dom'; +import tw from 'twin.macro'; + +import ServerManageContainer from '@/components/admin/servers/ServerManageContainer'; +import ServerStartupContainer from '@/components/admin/servers/ServerStartupContainer'; +import AdminContentBlock from '@/components/admin/AdminContentBlock'; +import Spinner from '@/components/elements/Spinner'; +import FlashMessageRender from '@/components/FlashMessageRender'; +import { SubNavigation, SubNavigationLink } from '@/components/admin/SubNavigation'; +import ServerSettingsContainer from '@/components/admin/servers/ServerSettingsContainer'; +import useFlash from '@/plugins/useFlash'; +import { useServerFromRoute } from '@/api/admin/server'; +import { AdjustmentsIcon, CogIcon, DatabaseIcon, FolderIcon, ShieldExclamationIcon } from '@heroicons/react/outline'; + +export default () => { + const params = useParams<'id'>(); + + const { clearFlashes, clearAndAddHttpError } = useFlash(); + const { data: server, error, isValidating, mutate } = useServerFromRoute(); + + useEffect(() => { + mutate(); + }, []); + + useEffect(() => { + if (!error) clearFlashes('server'); + if (error) clearAndAddHttpError({ key: 'server', error }); + }, [error]); + + if (!server || (error && isValidating)) { + return ( + + + + ); + } + + return ( + + +
    +
    +

    {server.name}

    +

    + {server.uuid} +

    +
    +
    + + + + + + + + + + + + + } /> + } /> + } /> + +
    + ); +}; diff --git a/resources/scripts/components/admin/servers/ServerSettingsContainer.tsx b/resources/scripts/components/admin/servers/ServerSettingsContainer.tsx new file mode 100644 index 0000000000..db094f884d --- /dev/null +++ b/resources/scripts/components/admin/servers/ServerSettingsContainer.tsx @@ -0,0 +1,103 @@ +import { useStoreActions } from 'easy-peasy'; +import type { FormikHelpers } from 'formik'; +import { Form, Formik } from 'formik'; +import tw from 'twin.macro'; +import { object } from 'yup'; + +import { useServerFromRoute } from '@/api/admin/server'; +import type { Values } from '@/api/admin/servers/updateServer'; +import updateServer from '@/api/admin/servers/updateServer'; +import ServerDeleteButton from '@/components/admin/servers/ServerDeleteButton'; +import BaseSettingsBox from '@/components/admin/servers/settings/BaseSettingsBox'; +import FeatureLimitsBox from '@/components/admin/servers/settings/FeatureLimitsBox'; +import NetworkingBox from '@/components/admin/servers/settings/NetworkingBox'; +import ServerResourceBox from '@/components/admin/servers/settings/ServerResourceBox'; +import Button from '@/components/elements/Button'; + +export default () => { + const { data: server } = useServerFromRoute(); + const { clearFlashes, clearAndAddHttpError } = useStoreActions(actions => actions.flashes); + + if (!server) return null; + + const submit = (values: Values, { setSubmitting, setFieldValue }: FormikHelpers) => { + clearFlashes('server'); + + // This value is inverted to have the switch be on when the + // OOM Killer is enabled, rather than when disabled. + values.limits.oomDisabled = !values.limits.oomDisabled; + + updateServer(server.id, values) + .then(() => { + // setServer({ ...server, ...s }); + + // TODO: Figure out how to properly clear react-selects for allocations. + setFieldValue('addAllocations', []); + setFieldValue('removeAllocations', []); + }) + .catch(error => { + console.error(error); + clearAndAddHttpError({ key: 'server', error }); + }) + .then(() => setSubmitting(false)); + }; + + return ( + + {({ isSubmitting, isValid }) => ( +
    +
    +
    + + + +
    +
    + +
    +
    + + +
    +
    +
    +
    +
    + )} +
    + ); +}; diff --git a/resources/scripts/components/admin/servers/ServerStartupContainer.tsx b/resources/scripts/components/admin/servers/ServerStartupContainer.tsx new file mode 100644 index 0000000000..d672c57e0b --- /dev/null +++ b/resources/scripts/components/admin/servers/ServerStartupContainer.tsx @@ -0,0 +1,258 @@ +import type { Actions } from 'easy-peasy'; +import { useStoreActions } from 'easy-peasy'; +import type { FormikHelpers } from 'formik'; +import { Form, Formik, useField, useFormikContext } from 'formik'; +import { useEffect, useState } from 'react'; +import tw from 'twin.macro'; +import { object } from 'yup'; + +import type { InferModel } from '@/api/admin'; +import type { Egg, EggVariable } from '@/api/admin/egg'; +import { getEgg } from '@/api/admin/egg'; +import type { Server } from '@/api/admin/server'; +import { useServerFromRoute } from '@/api/admin/server'; +import type { Values } from '@/api/admin/servers/updateServerStartup'; +import updateServerStartup from '@/api/admin/servers/updateServerStartup'; +import EggSelect from '@/components/admin/servers/EggSelect'; +import NestSelector from '@/components/admin/servers/NestSelector'; +import FormikSwitch from '@/components/elements/FormikSwitch'; +import Button from '@/components/elements/Button'; +import Input from '@/components/elements/Input'; +import AdminBox from '@/components/admin/AdminBox'; +import Field from '@/components/elements/Field'; +import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; +import Label from '@/components/elements/Label'; +import type { ApplicationStore } from '@/state'; + +function ServerStartupLineContainer({ egg, server }: { egg: Egg | null; server: Server }) { + const { isSubmitting, setFieldValue } = useFormikContext(); + + useEffect(() => { + if (egg === null) { + return; + } + + if (server.eggId === egg.id) { + setFieldValue('image', server.container.image); + setFieldValue('startup', server.container.startup || ''); + return; + } + + // Whenever the egg is changed, set the server's startup command to the egg's default. + setFieldValue('image', egg.dockerImages.length > 0 ? egg.dockerImages[0] : ''); + setFieldValue('startup', ''); + }, [egg]); + + return ( + + + +
    + +
    + +
    + + +
    +
    + ); +} + +export function ServerServiceContainer({ + egg, + setEgg, + nestId: _nestId, +}: { + egg: Egg | null; + setEgg: (value: Egg | null) => void; + nestId: number; +}) { + const { isSubmitting } = useFormikContext(); + + const [nestId, setNestId] = useState(_nestId); + + return ( + +
    + +
    +
    + +
    +
    + +
    +
    + ); +} + +export function ServerImageContainer() { + const { isSubmitting } = useFormikContext(); + + return ( + + + +
    +
    + +
    +
    +
    + ); +} + +export function ServerVariableContainer({ variable, value }: { variable: EggVariable; value?: string }) { + const key = 'environment.' + variable.environmentVariable; + + const [, , { setValue, setTouched }] = useField(key); + + const { isSubmitting } = useFormikContext(); + + useEffect(() => { + if (value === undefined) { + return; + } + + setValue(value); + setTouched(true); + }, [value]); + + return ( + {variable.name}

    }> + + + +
    + ); +} + +function ServerStartupForm({ + egg, + setEgg, + server, +}: { + egg: Egg | null; + setEgg: (value: Egg | null) => void; + server: Server; +}) { + const { + isSubmitting, + isValid, + values: { environment }, + } = useFormikContext(); + + return ( +
    +
    +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + {/* This ensures that no variables are rendered unless the environment has a value for the variable. */} + {egg?.relationships.variables + ?.filter(v => Object.keys(environment).find(e => e === v.environmentVariable) !== undefined) + .map((v, i) => ( + v.eggId === v2.eggId && v.environmentVariable === v2.environmentVariable, + )?.serverValue + } + /> + ))} +
    + +
    +
    + +
    +
    +
    +
    + ); +} + +export default () => { + const { data: server } = useServerFromRoute(); + const { clearFlashes, clearAndAddHttpError } = useStoreActions( + (actions: Actions) => actions.flashes, + ); + const [egg, setEgg] = useState | null>(null); + + useEffect(() => { + if (!server) return; + + getEgg(server.eggId) + .then(egg => setEgg(egg)) + .catch(error => console.error(error)); + }, [server?.eggId]); + + if (!server) return null; + + const submit = (values: Values, { setSubmitting }: FormikHelpers) => { + clearFlashes('server'); + + updateServerStartup(server.id, values) + // .then(s => { + // mutate(data => { ...data, ...s }); + // }) + .catch(error => { + console.error(error); + clearAndAddHttpError({ key: 'server', error }); + }) + .then(() => setSubmitting(false)); + }; + + return ( + , + image: server.container.image, + eggId: server.eggId, + skipScripts: false, + }} + validationSchema={object().shape({})} + > + + + ); +}; diff --git a/resources/scripts/components/admin/servers/ServersContainer.tsx b/resources/scripts/components/admin/servers/ServersContainer.tsx new file mode 100644 index 0000000000..3533ceff86 --- /dev/null +++ b/resources/scripts/components/admin/servers/ServersContainer.tsx @@ -0,0 +1,36 @@ +import { NavLink } from 'react-router-dom'; +import tw from 'twin.macro'; + +import FlashMessageRender from '@/components/FlashMessageRender'; +import AdminContentBlock from '@/components/admin/AdminContentBlock'; +import ServersTable from '@/components/admin/servers/ServersTable'; +import Button from '@/components/elements/Button'; + +function ServersContainer() { + return ( + +
    +
    +

    Servers

    +

    + All servers available on the system. +

    +
    + +
    + + + +
    +
    + + + + +
    + ); +} + +export default ServersContainer; diff --git a/resources/scripts/components/admin/servers/ServersTable.tsx b/resources/scripts/components/admin/servers/ServersTable.tsx new file mode 100644 index 0000000000..c41f0b527a --- /dev/null +++ b/resources/scripts/components/admin/servers/ServersTable.tsx @@ -0,0 +1,236 @@ +import type { ChangeEvent } from 'react'; +import { useContext, useEffect } from 'react'; +import { NavLink } from 'react-router-dom'; +import tw from 'twin.macro'; + +import type { Filters } from '@/api/admin/servers/getServers'; +import getServers, { Context as ServersContext } from '@/api/admin/servers/getServers'; +import AdminCheckbox from '@/components/admin/AdminCheckbox'; +import AdminTable, { + ContentWrapper, + Loading, + NoItems, + Pagination, + TableBody, + TableHead, + TableHeader, + useTableHooks, +} from '@/components/admin/AdminTable'; +import CopyOnClick from '@/components/elements/CopyOnClick'; +import { AdminContext } from '@/state/admin'; +import useFlash from '@/plugins/useFlash'; + +function RowCheckbox({ id }: { id: number }) { + const isChecked = AdminContext.useStoreState(state => state.servers.selectedServers.indexOf(id) >= 0); + const appendSelectedServer = AdminContext.useStoreActions(actions => actions.servers.appendSelectedServer); + const removeSelectedServer = AdminContext.useStoreActions(actions => actions.servers.removeSelectedServer); + + return ( + ) => { + if (e.currentTarget.checked) { + appendSelectedServer(id); + } else { + removeSelectedServer(id); + } + }} + /> + ); +} + +interface Props { + filters?: Filters; +} + +function ServersTable({ filters }: Props) { + const { clearFlashes, clearAndAddHttpError } = useFlash(); + + const { page, setPage, setFilters, sort, setSort, sortDirection } = useContext(ServersContext); + const { data: servers, error, isValidating } = getServers(['node', 'user']); + + const length = servers?.items?.length || 0; + + const setSelectedServers = AdminContext.useStoreActions(actions => actions.servers.setSelectedServers); + const selectedServerLength = AdminContext.useStoreState(state => state.servers.selectedServers.length); + + const onSelectAllClick = (e: ChangeEvent) => { + setSelectedServers(e.currentTarget.checked ? servers?.items?.map(server => server.id) || [] : []); + }; + + const onSearch = (query: string): Promise => { + return new Promise(resolve => { + if (query.length < 2) { + setFilters(filters || null); + } else { + setFilters({ ...filters, name: query }); + } + return resolve(); + }); + }; + + useEffect(() => { + setSelectedServers([]); + }, [page]); + + useEffect(() => { + if (!error) { + clearFlashes('servers'); + return; + } + + clearAndAddHttpError({ key: 'servers', error }); + }, [error]); + + return ( + + + +
    + + + setSort('uuidShort')} + /> + setSort('name')} + /> + setSort('owner_id')} + /> + setSort('node_id')} + /> + setSort('status')} + /> + + + + {servers !== undefined && + !error && + !isValidating && + length > 0 && + servers.items.map(server => ( + + + + + + + + {/* TODO: Have permission check for displaying user information. */} + + + {/* TODO: Have permission check for displaying node information. */} + + + + + ))} + +
    + + + + + {server.identifier} + + + + + {server.name} + + + +
    + {server.relations.user?.email} +
    + +
    + {server.relations.user?.uuid.split('-')[0]} +
    +
    +
    + +
    + {server.relations.node?.name} +
    + +
    + {server.relations.node?.fqdn} +
    +
    +
    + {server.status === 'installing' ? ( + + Installing + + ) : server.status === 'transferring' ? ( + + Transferring + + ) : server.status === 'suspended' ? ( + + Suspended + + ) : ( + + Active + + )} +
    + + {servers === undefined || (error && isValidating) ? ( + + ) : length < 1 ? ( + + ) : null} +
    +
    +
    +
    + ); +} + +export default ({ filters }: Props) => { + const hooks = useTableHooks(filters); + + return ( + + + + ); +}; diff --git a/resources/scripts/components/admin/servers/settings/BaseSettingsBox.tsx b/resources/scripts/components/admin/servers/settings/BaseSettingsBox.tsx new file mode 100644 index 0000000000..3d3b8911a8 --- /dev/null +++ b/resources/scripts/components/admin/servers/settings/BaseSettingsBox.tsx @@ -0,0 +1,31 @@ +import { faCogs } from '@fortawesome/free-solid-svg-icons'; +import { useFormikContext } from 'formik'; +import type { ReactNode } from 'react'; +import tw from 'twin.macro'; + +import { useServerFromRoute } from '@/api/admin/server'; +import AdminBox from '@/components/admin/AdminBox'; +import OwnerSelect from '@/components/admin/servers/OwnerSelect'; +import Field from '@/components/elements/Field'; + +export default ({ children }: { children?: ReactNode }) => { + const { data: server } = useServerFromRoute(); + const { isSubmitting } = useFormikContext(); + + return ( + +
    + + + + {children} +
    +
    + ); +}; diff --git a/resources/scripts/components/admin/servers/settings/FeatureLimitsBox.tsx b/resources/scripts/components/admin/servers/settings/FeatureLimitsBox.tsx new file mode 100644 index 0000000000..46219af0a6 --- /dev/null +++ b/resources/scripts/components/admin/servers/settings/FeatureLimitsBox.tsx @@ -0,0 +1,38 @@ +import { faConciergeBell } from '@fortawesome/free-solid-svg-icons'; +import { useFormikContext } from 'formik'; +import tw from 'twin.macro'; + +import AdminBox from '@/components/admin/AdminBox'; +import Field from '@/components/elements/Field'; + +export default () => { + const { isSubmitting } = useFormikContext(); + + return ( + +
    + + + +
    +
    + ); +}; diff --git a/resources/scripts/components/admin/servers/settings/NetworkingBox.tsx b/resources/scripts/components/admin/servers/settings/NetworkingBox.tsx new file mode 100644 index 0000000000..d3adcf0602 --- /dev/null +++ b/resources/scripts/components/admin/servers/settings/NetworkingBox.tsx @@ -0,0 +1,68 @@ +import { faNetworkWired } from '@fortawesome/free-solid-svg-icons'; +import { useFormikContext } from 'formik'; +import tw from 'twin.macro'; + +import getAllocations from '@/api/admin/nodes/getAllocations'; +import { useServerFromRoute } from '@/api/admin/server'; +import AdminBox from '@/components/admin/AdminBox'; +import Label from '@/components/elements/Label'; +import Select from '@/components/elements/Select'; +import type { Option } from '@/components/elements/SelectField'; +import SelectField, { AsyncSelectField } from '@/components/elements/SelectField'; + +export default () => { + const { isSubmitting } = useFormikContext(); + const { data: server } = useServerFromRoute(); + + const loadOptions = async (inputValue: string, callback: (options: Option[]) => void) => { + if (!server) { + // eslint-disable-next-line node/no-callback-literal + callback([] as Option[]); + return; + } + + const allocations = await getAllocations(server.nodeId, { ip: inputValue, server_id: '0' }); + + callback( + allocations.map(a => { + return { value: a.id.toString(), label: a.getDisplayText() }; + }), + ); + }; + + return ( + +
    +
    + + +
    + + { + return { value: a.id.toString(), label: a.getDisplayText() }; + }) || [] + } + isMulti + isSearchable + /> +
    +
    + ); +}; diff --git a/resources/scripts/components/admin/servers/settings/ServerResourceBox.tsx b/resources/scripts/components/admin/servers/settings/ServerResourceBox.tsx new file mode 100644 index 0000000000..69312ec3ea --- /dev/null +++ b/resources/scripts/components/admin/servers/settings/ServerResourceBox.tsx @@ -0,0 +1,73 @@ +import { faBalanceScale } from '@fortawesome/free-solid-svg-icons'; +import { useFormikContext } from 'formik'; +import tw from 'twin.macro'; + +import AdminBox from '@/components/admin/AdminBox'; +import Field from '@/components/elements/Field'; +import FormikSwitch from '@/components/elements/FormikSwitch'; + +export default () => { + const { isSubmitting } = useFormikContext(); + + return ( + +
    + + + + + + +
    + +
    +
    +
    + ); +}; diff --git a/resources/scripts/components/admin/settings/GeneralSettings.tsx b/resources/scripts/components/admin/settings/GeneralSettings.tsx new file mode 100644 index 0000000000..eb4ece2c8c --- /dev/null +++ b/resources/scripts/components/admin/settings/GeneralSettings.tsx @@ -0,0 +1,37 @@ +import { Form, Formik } from 'formik'; +import tw from 'twin.macro'; + +import AdminBox from '@/components/admin/AdminBox'; +import Field, { FieldRow } from '@/components/elements/Field'; + +export default () => { + const submit = () => { + // + }; + + return ( + +
    +
    + + + + + + + + + + + +
    +
    +
    + ); +}; diff --git a/resources/scripts/components/admin/settings/MailSettings.tsx b/resources/scripts/components/admin/settings/MailSettings.tsx new file mode 100644 index 0000000000..1e63eecc2c --- /dev/null +++ b/resources/scripts/components/admin/settings/MailSettings.tsx @@ -0,0 +1,102 @@ +import { Form, Formik } from 'formik'; +import tw from 'twin.macro'; + +import AdminBox from '@/components/admin/AdminBox'; +import Button from '@/components/elements/Button'; +import Field, { FieldRow } from '@/components/elements/Field'; +import Label from '@/components/elements/Label'; +import Select from '@/components/elements/Select'; + +export default () => { + const submit = () => { + // + }; + + return ( + + {({ isSubmitting, isValid }) => ( +
    + + + + +
    + + +
    +
    + + + + + + + + + + +
    + +
    +
    + +
    +
    +
    + )} +
    + ); +}; diff --git a/resources/scripts/components/admin/settings/SettingsContainer.tsx b/resources/scripts/components/admin/settings/SettingsContainer.tsx new file mode 100644 index 0000000000..3fab069569 --- /dev/null +++ b/resources/scripts/components/admin/settings/SettingsContainer.tsx @@ -0,0 +1,52 @@ +import { AdjustmentsIcon, ChipIcon, CodeIcon, MailIcon, ShieldCheckIcon } from '@heroicons/react/outline'; +import { Route, Routes } from 'react-router-dom'; +import tw from 'twin.macro'; + +import AdminContentBlock from '@/components/admin/AdminContentBlock'; +import MailSettings from '@/components/admin/settings/MailSettings'; +import FlashMessageRender from '@/components/FlashMessageRender'; +import { SubNavigation, SubNavigationLink } from '@/components/admin/SubNavigation'; +import GeneralSettings from '@/components/admin/settings/GeneralSettings'; + +export default () => { + return ( + +
    +
    +

    Settings

    +

    + Configure and manage settings for Pterodactyl. +

    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + } /> + } /> + Security

    } /> + Features

    } /> + Advanced

    } /> +
    +
    + ); +}; diff --git a/resources/scripts/components/admin/users/NewUserContainer.tsx b/resources/scripts/components/admin/users/NewUserContainer.tsx new file mode 100644 index 0000000000..36fdf29f26 --- /dev/null +++ b/resources/scripts/components/admin/users/NewUserContainer.tsx @@ -0,0 +1,49 @@ +import type { Actions } from 'easy-peasy'; +import { useStoreActions } from 'easy-peasy'; +import type { FormikHelpers } from 'formik'; +import { useNavigate } from 'react-router-dom'; +import tw from 'twin.macro'; + +import type { UpdateUserValues } from '@/api/admin/users'; +import { createUser } from '@/api/admin/users'; +import AdminContentBlock from '@/components/admin/AdminContentBlock'; +import UserForm from '@/components/admin/users/UserForm'; +import FlashMessageRender from '@/components/FlashMessageRender'; +import type { ApplicationStore } from '@/state'; + +export default () => { + const navigate = useNavigate(); + + const { clearFlashes, clearAndAddHttpError } = useStoreActions( + (actions: Actions) => actions.flashes, + ); + + const submit = (values: UpdateUserValues, { setSubmitting }: FormikHelpers) => { + clearFlashes('user:create'); + + createUser(values) + .then(user => navigate(`/admin/users/${user.id}`)) + .catch(error => { + console.error(error); + clearAndAddHttpError({ key: 'user:create', error }); + }) + .then(() => setSubmitting(false)); + }; + + return ( + +
    +
    +

    New User

    +

    + Add a new user to the panel. +

    +
    +
    + + + + +
    + ); +}; diff --git a/resources/scripts/components/admin/users/RoleSelect.tsx b/resources/scripts/components/admin/users/RoleSelect.tsx new file mode 100644 index 0000000000..11ab7aa166 --- /dev/null +++ b/resources/scripts/components/admin/users/RoleSelect.tsx @@ -0,0 +1,56 @@ +import { useFormikContext } from 'formik'; +import { useState } from 'react'; + +import { searchRoles } from '@/api/admin/roles'; +import SearchableSelect, { Option } from '@/components/elements/SearchableSelect'; +import type { UserRole } from '@definitions/admin'; + +export default ({ selected }: { selected: UserRole | null }) => { + const context = useFormikContext(); + + const [role, setRole] = useState(selected); + const [roles, setRoles] = useState(null); + + const onSearch = (query: string): Promise => { + return new Promise((resolve, reject) => { + searchRoles({ name: query }) + .then(roles => { + setRoles(roles); + return resolve(); + }) + .catch(reject); + }); + }; + + const onSelect = (role: UserRole | null) => { + setRole(role); + context.setFieldValue('adminRoleId', role?.id || null); + }; + + const getSelectedText = (role: UserRole | null): string | undefined => { + return role?.name; + }; + + return ( + + {roles?.map(d => ( + + ))} + + ); +}; diff --git a/resources/scripts/components/admin/users/UserAboutContainer.tsx b/resources/scripts/components/admin/users/UserAboutContainer.tsx new file mode 100644 index 0000000000..623e6b87cd --- /dev/null +++ b/resources/scripts/components/admin/users/UserAboutContainer.tsx @@ -0,0 +1,62 @@ +import type { Actions } from 'easy-peasy'; +import { useStoreActions } from 'easy-peasy'; +import type { FormikHelpers } from 'formik'; +import { useNavigate } from 'react-router-dom'; + +import type { UpdateUserValues } from '@/api/admin/users'; +import { updateUser } from '@/api/admin/users'; +import UserDeleteButton from '@/components/admin/users/UserDeleteButton'; +import UserForm from '@/components/admin/users/UserForm'; +import { Context } from '@/components/admin/users/UserRouter'; +import type { ApplicationStore } from '@/state'; +import tw from 'twin.macro'; + +const UserAboutContainer = () => { + const navigate = useNavigate(); + + const { clearFlashes, clearAndAddHttpError } = useStoreActions( + (actions: Actions) => actions.flashes, + ); + + const user = Context.useStoreState(state => state.user); + const setUser = Context.useStoreActions(actions => actions.setUser); + + if (user === undefined) { + return <>; + } + + const submit = (values: UpdateUserValues, { setSubmitting }: FormikHelpers) => { + clearFlashes('user'); + + updateUser(user.id, values) + .then(() => setUser({ ...user, ...values })) + .catch(error => { + console.error(error); + clearAndAddHttpError({ key: 'user', error }); + }) + .then(() => setSubmitting(false)); + }; + + return ( + +
    + navigate('/admin/users')} /> +
    +
    + ); +}; + +export default UserAboutContainer; diff --git a/resources/scripts/components/admin/users/UserDeleteButton.tsx b/resources/scripts/components/admin/users/UserDeleteButton.tsx new file mode 100644 index 0000000000..8911a5638e --- /dev/null +++ b/resources/scripts/components/admin/users/UserDeleteButton.tsx @@ -0,0 +1,73 @@ +import type { Actions } from 'easy-peasy'; +import { useStoreActions } from 'easy-peasy'; +import { useState } from 'react'; +import tw from 'twin.macro'; + +import { deleteUser } from '@/api/admin/users'; +import Button from '@/components/elements/Button'; +import ConfirmationModal from '@/components/elements/ConfirmationModal'; +import type { ApplicationStore } from '@/state'; + +interface Props { + userId: number; + onDeleted: () => void; +} + +export default ({ userId, onDeleted }: Props) => { + const [visible, setVisible] = useState(false); + const [loading, setLoading] = useState(false); + + const { clearFlashes, clearAndAddHttpError } = useStoreActions( + (actions: Actions) => actions.flashes, + ); + + const onDelete = () => { + setLoading(true); + clearFlashes('user'); + + deleteUser(userId) + .then(() => { + setLoading(false); + onDeleted(); + }) + .catch(error => { + console.error(error); + clearAndAddHttpError({ key: 'user', error }); + + setLoading(false); + setVisible(false); + }); + }; + + return ( + <> + setVisible(false)} + > + Are you sure you want to delete this user? + + + + + ); +}; diff --git a/resources/scripts/components/admin/users/UserForm.tsx b/resources/scripts/components/admin/users/UserForm.tsx new file mode 100644 index 0000000000..1c8be4b388 --- /dev/null +++ b/resources/scripts/components/admin/users/UserForm.tsx @@ -0,0 +1,148 @@ +import type { Action } from 'easy-peasy'; +import { action, createContextStore } from 'easy-peasy'; +import type { FormikHelpers } from 'formik'; +import { Form, Formik } from 'formik'; +import tw from 'twin.macro'; +import { bool, object, string } from 'yup'; + +import type { UpdateUserValues } from '@/api/admin/users'; +import AdminBox from '@/components/admin/AdminBox'; +import RoleSelect from '@/components/admin/users/RoleSelect'; +import CopyOnClick from '@/components/elements/CopyOnClick'; +import FormikSwitch from '@/components/elements/FormikSwitch'; +import Input from '@/components/elements/Input'; +import Label from '@/components/elements/Label'; +import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; +import Button from '@/components/elements/Button'; +import Field, { FieldRow } from '@/components/elements/Field'; +import type { User, UserRole } from '@definitions/admin'; + +interface ctx { + user: User | undefined; + setUser: Action; +} + +export const Context = createContextStore({ + user: undefined, + + setUser: action((state, payload) => { + state.user = payload; + }), +}); + +export interface Params { + title: string; + initialValues?: UpdateUserValues; + children?: React.ReactNode; + + onSubmit: (values: UpdateUserValues, helpers: FormikHelpers) => void; + + uuid?: string; + role: UserRole | null; +} + +export default function UserForm({ title, initialValues, children, onSubmit, uuid, role }: Params) { + const submit = (values: UpdateUserValues, helpers: FormikHelpers) => { + onSubmit(values, helpers); + }; + + if (!initialValues) { + initialValues = { + externalId: '', + username: '', + email: '', + password: '', + adminRoleId: null, + rootAdmin: false, + }; + } + + return ( + + {({ isSubmitting, isValid }) => ( + <> + + + +
    + + {uuid && ( +
    + + + + +
    + )} + + + + + +
    + + {/* TODO: Remove toggle once role permissions are implemented. */} +
    +
    + +
    +
    + +
    + {children} +
    + +
    +
    +
    +
    + + )} +
    + ); +} diff --git a/resources/scripts/components/admin/users/UserRouter.tsx b/resources/scripts/components/admin/users/UserRouter.tsx new file mode 100644 index 0000000000..b6f5554bf8 --- /dev/null +++ b/resources/scripts/components/admin/users/UserRouter.tsx @@ -0,0 +1,114 @@ +import type { Action, Actions } from 'easy-peasy'; +import { action, createContextStore, useStoreActions } from 'easy-peasy'; +import { useEffect, useState } from 'react'; +import { Route, Routes, useParams } from 'react-router-dom'; +import tw from 'twin.macro'; + +import { getUser } from '@/api/admin/users'; +import AdminContentBlock from '@/components/admin/AdminContentBlock'; +import { SubNavigation, SubNavigationLink } from '@/components/admin/SubNavigation'; +import UserAboutContainer from '@/components/admin/users/UserAboutContainer'; +import UserServers from '@/components/admin/users/UserServers'; +import Spinner from '@/components/elements/Spinner'; +import FlashMessageRender from '@/components/FlashMessageRender'; +import type { ApplicationStore } from '@/state'; +import type { User } from '@definitions/admin'; + +interface ctx { + user: User | undefined; + setUser: Action; +} + +export const Context = createContextStore({ + user: undefined, + + setUser: action((state, payload) => { + state.user = payload; + }), +}); + +const UserRouter = () => { + const params = useParams<'id'>(); + + const { clearFlashes, clearAndAddHttpError } = useStoreActions( + (actions: Actions) => actions.flashes, + ); + const [loading, setLoading] = useState(true); + + const user = Context.useStoreState(state => state.user); + const setUser = Context.useStoreActions(actions => actions.setUser); + + useEffect(() => { + clearFlashes('user'); + + getUser(Number(params.id), ['role']) + .then(user => setUser(user)) + .catch(error => { + console.error(error); + clearAndAddHttpError({ key: 'user', error }); + }) + .then(() => setLoading(false)); + }, []); + + if (loading || user === undefined) { + return ( + + + +
    + +
    +
    + ); + } + + return ( + +
    +
    +

    {user.email}

    +

    + {user.uuid} +

    +
    +
    + + + + + + + + + + + + + + + + + + + } /> + } /> + +
    + ); +}; + +export default () => { + return ( + + + + ); +}; diff --git a/resources/scripts/components/admin/users/UserServers.tsx b/resources/scripts/components/admin/users/UserServers.tsx new file mode 100644 index 0000000000..e4d3157f42 --- /dev/null +++ b/resources/scripts/components/admin/users/UserServers.tsx @@ -0,0 +1,10 @@ +import ServersTable from '@/components/admin/servers/ServersTable'; +import { Context } from '@/components/admin/users/UserRouter'; + +function UserServers() { + const user = Context.useStoreState(state => state.user); + + return ; +} + +export default UserServers; diff --git a/resources/scripts/components/admin/users/UserTableRow.tsx b/resources/scripts/components/admin/users/UserTableRow.tsx new file mode 100644 index 0000000000..ee8baf729e --- /dev/null +++ b/resources/scripts/components/admin/users/UserTableRow.tsx @@ -0,0 +1,79 @@ +import { BanIcon, DotsVerticalIcon, LockOpenIcon, PencilIcon, SupportIcon, TrashIcon } from '@heroicons/react/solid'; +import { useState } from 'react'; + +import Checkbox from '@/components/elements/inputs/Checkbox'; +import { Dropdown } from '@/components/elements/dropdown'; +import { Dialog } from '@/components/elements/dialog'; +import { Button } from '@/components/elements/button'; +import { User } from '@definitions/admin'; + +interface Props { + user: User; + selected?: boolean; + onRowChange: (user: User, selected: boolean) => void; +} + +const UserTableRow = ({ user, selected, onRowChange }: Props) => { + const [visible, setVisible] = useState(false); + + return ( + <> + setVisible(false)}> + + This account will be permanently deleted. + + setVisible(false)}>Cancel + Delete + + + + +
    + onRowChange(user, e.currentTarget.checked)} /> +
    + + +
    +
    + {'User +
    +
    +

    {user.email}

    +

    {user.uuid}

    +
    +
    + + + {user.isUsingTwoFactor && ( + + 2-FA Enabled + + )} + + + + + + + }>Edit + }>Reset Password + } disabled={!user.isUsingTwoFactor}> + Disable 2-FA + + }>Suspend + + } onClick={() => setVisible(true)} danger> + Delete Account + + + + + + ); +}; + +export default UserTableRow; diff --git a/resources/scripts/components/admin/users/UsersContainer.tsx b/resources/scripts/components/admin/users/UsersContainer.tsx new file mode 100644 index 0000000000..b361722dd5 --- /dev/null +++ b/resources/scripts/components/admin/users/UsersContainer.tsx @@ -0,0 +1,119 @@ +import { LockOpenIcon, PlusIcon, SupportIcon, TrashIcon } from '@heroicons/react/solid'; +import { Fragment, useEffect, useState } from 'react'; + +import { useGetUsers } from '@/api/admin/users'; +import type { UUID } from '@/api/definitions'; +import { Transition } from '@/components/elements/transitions'; +import { Button } from '@/components/elements/button/index'; +import Checkbox from '@/components/elements/inputs/Checkbox'; +import InputField from '@/components/elements/inputs/InputField'; +import UserTableRow from '@/components/admin/users/UserTableRow'; +import TFootPaginated from '@/components/elements/table/TFootPaginated'; +import type { User } from '@definitions/admin'; +import extractSearchFilters from '@/helpers/extractSearchFilters'; +import useDebouncedState from '@/plugins/useDebouncedState'; + +const filters = ['id', 'uuid', 'external_id', 'username', 'email'] as const; + +const UsersContainer = () => { + const [search, setSearch] = useDebouncedState('', 500); + const [selected, setSelected] = useState([]); + const { data: users } = useGetUsers( + extractSearchFilters(search, filters, { + splitUnmatched: true, + returnUnmatched: true, + }), + ); + + useEffect(() => { + document.title = 'Admin | Users'; + }, []); + + const onRowChange = (user: User, checked: boolean) => { + setSelected(state => { + return checked ? [...state, user.uuid] : selected.filter(uuid => uuid !== user.uuid); + }); + }; + + const selectAllChecked = users && users.items.length > 0 && selected.length > 0; + const onSelectAll = () => + setSelected(state => (state.length > 0 ? [] : users?.items.map(({ uuid }) => uuid) || [])); + + return ( +
    +
    + +
    +
    +
    + +
    +
    + setSearch(e.currentTarget.value)} + /> +
    + 0} duration={'duration-75'}> +
    +
    + +
    + + + + + + + + + +
    +
    +
    + + + + + + + + {users?.items.map(user => ( + + ))} + + {users && } +
    + + Email + + +
    +
    + ); +}; + +export default UsersContainer; diff --git a/resources/scripts/components/dashboard/activity/ActivityLogContainer.tsx b/resources/scripts/components/dashboard/activity/ActivityLogContainer.tsx index aaf8ccd642..bc59952844 100644 --- a/resources/scripts/components/dashboard/activity/ActivityLogContainer.tsx +++ b/resources/scripts/components/dashboard/activity/ActivityLogContainer.tsx @@ -47,7 +47,7 @@ export default () => { {!data && isValidating ? ( ) : ( -
    +
    {data?.items.map(activity => ( {typeof activity.properties.useragent === 'string' && ( diff --git a/resources/scripts/components/dashboard/forms/RecoveryTokensDialog.tsx b/resources/scripts/components/dashboard/forms/RecoveryTokensDialog.tsx index cddd5d55ab..13208670ad 100644 --- a/resources/scripts/components/dashboard/forms/RecoveryTokensDialog.tsx +++ b/resources/scripts/components/dashboard/forms/RecoveryTokensDialog.tsx @@ -28,13 +28,13 @@ export default ({ tokens, open, onClose }: RecoveryTokenDialogProps) => { > -
    +                
                         {grouped.map(value => (
                             
                                 {value[0]}
    -                             
    +                             
                                 {value[1]}
    -                             
    +                             
                             
                         ))}
                     
    diff --git a/resources/scripts/components/dashboard/forms/SetupTOTPDialog.tsx b/resources/scripts/components/dashboard/forms/SetupTOTPDialog.tsx index f10b1036d2..5f5616d5e8 100644 --- a/resources/scripts/components/dashboard/forms/SetupTOTPDialog.tsx +++ b/resources/scripts/components/dashboard/forms/SetupTOTPDialog.tsx @@ -62,7 +62,7 @@ const ConfigureTwoFactorForm = ({ onTokens }: Props) => { return (
    -
    +
    {!token ? ( ) : ( @@ -70,7 +70,7 @@ const ConfigureTwoFactorForm = ({ onTokens }: Props) => { )}
    -

    +

    {token?.secret.match(/.{1,4}/g)!.join(' ') || 'Loading...'}

    diff --git a/resources/scripts/components/elements/Code.tsx b/resources/scripts/components/elements/Code.tsx index 02640d699e..90c6c78351 100644 --- a/resources/scripts/components/elements/Code.tsx +++ b/resources/scripts/components/elements/Code.tsx @@ -11,7 +11,7 @@ export default ({ dark, className, children }: CodeProps) => ( {children} diff --git a/resources/scripts/components/elements/CopyOnClick.tsx b/resources/scripts/components/elements/CopyOnClick.tsx index 80c01277c6..50ed4fcb3e 100644 --- a/resources/scripts/components/elements/CopyOnClick.tsx +++ b/resources/scripts/components/elements/CopyOnClick.tsx @@ -51,7 +51,7 @@ const CopyOnClick = ({ text, showInNotification = true, children }: CopyOnClickP
    -
    +

    {showInNotification ? `Copied "${String(text)}" to clipboard.` diff --git a/resources/scripts/components/elements/Editor.tsx b/resources/scripts/components/elements/Editor.tsx new file mode 100644 index 0000000000..c3a77b9c68 --- /dev/null +++ b/resources/scripts/components/elements/Editor.tsx @@ -0,0 +1,318 @@ +import { autocompletion, completionKeymap } from '@codemirror/autocomplete'; +import { closeBrackets, closeBracketsKeymap } from '@codemirror/closebrackets'; +import { defaultKeymap, indentWithTab } from '@codemirror/commands'; +import { commentKeymap } from '@codemirror/comment'; +import { foldGutter, foldKeymap } from '@codemirror/fold'; +import { lineNumbers, highlightActiveLineGutter } from '@codemirror/gutter'; +import { defaultHighlightStyle } from '@codemirror/highlight'; +import { history, historyKeymap } from '@codemirror/history'; +import { indentOnInput, LanguageSupport, LRLanguage, indentUnit } from '@codemirror/language'; +import { lintKeymap } from '@codemirror/lint'; +import { bracketMatching } from '@codemirror/matchbrackets'; +import { rectangularSelection } from '@codemirror/rectangular-selection'; +import { searchKeymap, highlightSelectionMatches } from '@codemirror/search'; +import { Compartment, Extension, EditorState } from '@codemirror/state'; +import { StreamLanguage, StreamParser } from '@codemirror/stream-parser'; +import { keymap, highlightSpecialChars, drawSelection, highlightActiveLine, EditorView } from '@codemirror/view'; +import { clike } from '@codemirror/legacy-modes/mode/clike'; +import { cpp } from '@codemirror/lang-cpp'; +import { css } from '@codemirror/lang-css'; +import { Cassandra, MariaSQL, MSSQL, MySQL, PostgreSQL, sql, SQLite, StandardSQL } from '@codemirror/lang-sql'; +import { diff } from '@codemirror/legacy-modes/mode/diff'; +import { dockerFile } from '@codemirror/legacy-modes/mode/dockerfile'; +import { markdown, markdownLanguage } from '@codemirror/lang-markdown'; +import { go } from '@codemirror/legacy-modes/mode/go'; +import { html } from '@codemirror/lang-html'; +import { http } from '@codemirror/legacy-modes/mode/http'; +import { javascript, typescriptLanguage } from '@codemirror/lang-javascript'; +import { json } from '@codemirror/lang-json'; +import { lua } from '@codemirror/legacy-modes/mode/lua'; +import { properties } from '@codemirror/legacy-modes/mode/properties'; +import { python } from '@codemirror/legacy-modes/mode/python'; +import { ruby } from '@codemirror/legacy-modes/mode/ruby'; +import { rust } from '@codemirror/lang-rust'; +import { shell } from '@codemirror/legacy-modes/mode/shell'; +import { toml } from '@codemirror/legacy-modes/mode/toml'; +import { xml } from '@codemirror/lang-xml'; +import { yaml } from '@codemirror/legacy-modes/mode/yaml'; +import React, { useCallback, useEffect, useState } from 'react'; +import tw, { styled, TwStyle } from 'twin.macro'; +import { ayuMirage } from '@/components/elements/EditorTheme'; + +type EditorMode = LanguageSupport | LRLanguage | StreamParser; + +export interface Mode { + name: string; + mime: string; + mimes?: string[]; + mode?: EditorMode; + ext?: string[]; + alias?: string[]; + file?: RegExp; +} + +export const modes: Mode[] = [ + { name: 'C', mime: 'text/x-csrc', mode: clike({}), ext: [ 'c', 'h', 'ino' ] }, + { name: 'C++', mime: 'text/x-c++src', mode: cpp(), ext: [ 'cpp', 'c++', 'cc', 'cxx', 'hpp', 'h++', 'hh', 'hxx' ], alias: [ 'cpp' ] }, + { name: 'C#', mime: 'text/x-csharp', mode: clike({}), ext: [ 'cs' ], alias: [ 'csharp', 'cs' ] }, + { name: 'CSS', mime: 'text/css', mode: css(), ext: [ 'css' ] }, + { name: 'CQL', mime: 'text/x-cassandra', mode: sql({ dialect: Cassandra }), ext: [ 'cql' ] }, + { name: 'Diff', mime: 'text/x-diff', mode: diff, ext: [ 'diff', 'patch' ] }, + { name: 'Dockerfile', mime: 'text/x-dockerfile', mode: dockerFile, file: /^Dockerfile$/ }, + { name: 'Git Markdown', mime: 'text/x-gfm', mode: markdown({ defaultCodeLanguage: markdownLanguage }), file: /^(readme|contributing|history|license).md$/i }, + { name: 'Golang', mime: 'text/x-go', mode: go, ext: [ 'go' ] }, + { name: 'HTML', mime: 'text/html', mode: html(), ext: [ 'html', 'htm', 'handlebars', 'hbs' ], alias: [ 'xhtml' ] }, + { name: 'HTTP', mime: 'message/http', mode: http }, + { name: 'JavaScript', mime: 'text/javascript', mimes: [ 'text/javascript', 'text/ecmascript', 'application/javascript', 'application/x-javascript', 'application/ecmascript' ], mode: javascript(), ext: [ 'js' ], alias: [ 'ecmascript', 'js', 'node' ] }, + { name: 'JSON', mime: 'application/json', mimes: [ 'application/json', 'application/x-json' ], mode: json(), ext: [ 'json', 'json5', 'map' ], alias: [ 'json5' ] }, + { name: 'Lua', mime: 'text/x-lua', mode: lua, ext: [ 'lua' ] }, + { name: 'Markdown', mime: 'text/x-markdown', mode: markdown({ defaultCodeLanguage: markdownLanguage }), ext: [ 'markdown', 'md', 'mkd' ] }, + { name: 'MariaDB', mime: 'text/x-mariadb', mode: sql({ dialect: MariaSQL }) }, + { name: 'MS SQL', mime: 'text/x-mssql', mode: sql({ dialect: MSSQL }) }, + { name: 'MySQL', mime: 'text/x-mysql', mode: sql({ dialect: MySQL }) }, + { name: 'Plain Text', mime: 'text/plain', mode: undefined, ext: [ 'txt', 'text', 'conf', 'def', 'list', 'log' ] }, + { name: 'PostgreSQL', mime: 'text/x-pgsql', mode: sql({ dialect: PostgreSQL }) }, + { name: 'Properties', mime: 'text/x-properties', mode: properties, ext: [ 'properties', 'ini', 'in' ], alias: [ 'ini', 'properties' ] }, + { name: 'Python', mime: 'text/x-python', mode: python, ext: [ 'BUILD', 'bzl', 'py', 'pyw' ], file: /^(BUCK|BUILD)$/ }, + { name: 'Ruby', mime: 'text/x-ruby', mode: ruby, ext: [ 'rb' ], alias: [ 'jruby', 'macruby', 'rake', 'rb', 'rbx' ] }, + { name: 'Rust', mime: 'text/x-rustsrc', mode: rust(), ext: [ 'rs' ] }, + { name: 'Sass', mime: 'text/x-sass', mode: css(), ext: [ 'sass' ] }, + { name: 'SCSS', mime: 'text/x-scss', mode: css(), ext: [ 'scss' ] }, + { name: 'Shell', mime: 'text/x-sh', mimes: [ 'text/x-sh', 'application/x-sh' ], mode: shell, ext: [ 'sh', 'ksh', 'bash' ], alias: [ 'bash', 'sh', 'zsh' ], file: /^PKGBUILD$/ }, + { name: 'SQL', mime: 'text/x-sql', mode: sql({ dialect: StandardSQL }), ext: [ 'sql' ] }, + { name: 'SQLite', mime: 'text/x-sqlite', mode: sql({ dialect: SQLite }) }, + { name: 'TOML', mime: 'text/x-toml', mode: toml, ext: [ 'toml' ] }, + { name: 'TypeScript', mime: 'application/typescript', mode: typescriptLanguage, ext: [ 'ts' ], alias: [ 'ts' ] }, + { name: 'XML', mime: 'application/xml', mimes: [ 'application/xml', 'text/xml' ], mode: xml(), ext: [ 'xml', 'xsl', 'xsd', 'svg' ], alias: [ 'rss', 'wsdl', 'xsd' ] }, + { name: 'YAML', mime: 'text/x-yaml', mimes: [ 'text/x-yaml', 'text/yaml' ], mode: yaml, ext: [ 'yaml', 'yml' ], alias: [ 'yml' ] }, +]; + +export const modeToExtension = (m: EditorMode): Extension => { + if (m instanceof LanguageSupport) { + return m; + } + + if (m instanceof LRLanguage) { + return m; + } + + return StreamLanguage.define(m); +}; + +const findModeByFilename = (filename: string): Mode => { + for (let i = 0; i < modes.length; i++) { + const info = modes[i]; + + if (info.file && info.file.test(filename)) { + return info; + } + } + + const dot = filename.lastIndexOf('.'); + const ext = dot > -1 && filename.substring(dot + 1, filename.length); + + if (ext) { + for (let i = 0; i < modes.length; i++) { + const info = modes[i]; + if (info.ext) { + for (let j = 0; j < info.ext.length; j++) { + if (info.ext[j] === ext) { + return info; + } + } + } + } + } + + const plainText = modes.find(m => m.mime === 'text/plain'); + if (plainText === undefined) { + throw new Error('failed to find \'text/plain\' mode'); + } + return plainText; +}; + +const findLanguageExtensionByMode = (mode: Mode): Extension => { + if (mode.mode === undefined) { + return []; + } + return modeToExtension(mode.mode); +}; + +const defaultExtensions: Extension = [ + ayuMirage, + + lineNumbers(), + highlightActiveLineGutter(), + highlightSpecialChars(), + history(), + foldGutter(), + drawSelection(), + EditorState.allowMultipleSelections.of(true), + indentOnInput(), + defaultHighlightStyle.fallback, + bracketMatching(), + closeBrackets(), + autocompletion(), + rectangularSelection(), + highlightActiveLine(), + highlightSelectionMatches(), + keymap.of([ + ...closeBracketsKeymap, + ...defaultKeymap, + ...searchKeymap, + ...historyKeymap, + ...foldKeymap, + ...commentKeymap, + ...completionKeymap, + ...lintKeymap, + indentWithTab, + ]), + EditorState.tabSize.of(4), + // This is gonna piss people off, but that isn't my problem. + indentUnit.of('\t'), +]; + +const EditorContainer = styled.div<{ overrides?: TwStyle }>` + //min-height: 12rem; + ${tw`relative`}; + + & > div { + ${props => props.overrides}; + + &.cm-focused { + outline: none; + } + } +`; + +export interface Props { + className?: string; + style?: React.CSSProperties; + overrides?: TwStyle; + + initialContent?: string; + extensions?: Extension[]; + mode?: EditorMode; + + filename?: string; + onModeChanged?: (mode: Mode) => void; + fetchContent?: (callback: () => Promise) => void; + onContentSaved?: () => void; +} + +export default ({ className, style, overrides, initialContent, extensions, mode, filename, onModeChanged, fetchContent, onContentSaved }: Props) => { + const [ languageConfig ] = useState(new Compartment()); + const [ keybinds ] = useState(new Compartment()); + const [ view, setView ] = useState(); + + const createEditorState = () => { + return EditorState.create({ + doc: initialContent, + extensions: [ + ...defaultExtensions, + ...(extensions !== undefined ? extensions : []), + + languageConfig.of(mode !== undefined ? modeToExtension(mode) : findLanguageExtensionByMode(findModeByFilename(filename || ''))), + keybinds.of([]), + ], + }); + }; + + const ref = useCallback((node) => { + if (!node) { + return; + } + + const view = new EditorView({ + state: createEditorState(), + parent: node, + }); + setView(view); + }, []); + + // This useEffect is required to send the proper mode back to the parent element + // due to the initial language being set with EditorState#create, rather than in + // an useEffect like this one, or one watching `filename`. + useEffect(() => { + if (onModeChanged === undefined) { + return; + } + + onModeChanged(findModeByFilename(filename || '')); + }, []); + + useEffect(() => { + if (view === undefined) { + return; + } + + if (mode === undefined) { + return; + } + + view.dispatch({ + effects: languageConfig.reconfigure(modeToExtension(mode)), + }); + }, [ mode ]); + + useEffect(() => { + if (view === undefined) { + return; + } + + if (filename === undefined) { + return; + } + + const mode = findModeByFilename(filename || ''); + + view.dispatch({ + effects: languageConfig.reconfigure(findLanguageExtensionByMode(mode)), + }); + + if (onModeChanged !== undefined) { + onModeChanged(mode); + } + }, [ filename ]); + + useEffect(() => { + if (view === undefined) { + return; + } + + // We could dispatch a view update to replace the content, but this would keep the edit history, + // and previously would duplicate the content of the editor. + view.setState(createEditorState()); + }, [ initialContent ]); + + useEffect(() => { + if (fetchContent === undefined) { + return; + } + + if (!view) { + fetchContent(() => Promise.reject(new Error('no editor session has been configured'))); + return; + } + + if (onContentSaved !== undefined) { + view.dispatch({ + effects: keybinds.reconfigure(keymap.of([ + { + key: 'Mod-s', + run: () => { + onContentSaved(); + return true; + }, + }, + ])), + }); + } + + fetchContent(() => Promise.resolve(view.state.doc.toString())); + }, [ view, fetchContent, onContentSaved ]); + + return ( + + ); +}; diff --git a/resources/scripts/components/elements/Field.tsx b/resources/scripts/components/elements/Field.tsx index 3ce78349a4..907689cb77 100644 --- a/resources/scripts/components/elements/Field.tsx +++ b/resources/scripts/components/elements/Field.tsx @@ -1,8 +1,12 @@ +import type { FieldProps } from 'formik'; +import { Field as FormikField } from 'formik'; +import type { InputHTMLAttributes, TextareaHTMLAttributes } from 'react'; import { forwardRef } from 'react'; -import * as React from 'react'; -import { Field as FormikField, FieldProps } from 'formik'; -import Input from '@/components/elements/Input'; +import tw, { styled } from 'twin.macro'; + import Label from '@/components/elements/Label'; +import Input, { Textarea } from '@/components/elements/Input'; +import InputError from '@/components/elements/InputError'; interface OwnProps { name: string; @@ -12,7 +16,7 @@ interface OwnProps { validate?: (value: any) => undefined | string | Promise; } -type Props = OwnProps & Omit, 'name'>; +type Props = OwnProps & Omit, 'name'>; const Field = forwardRef( ({ id, name, light = false, label, description, validate, ...props }, ref) => ( @@ -47,3 +51,42 @@ const Field = forwardRef( Field.displayName = 'Field'; export default Field; + +type TextareaProps = OwnProps & Omit, 'name'>; + +export const TextareaField = forwardRef(function TextareaField( + { id, name, light = false, label, description, validate, className, ...props }, + ref, +) { + return ( + + {({ field, form: { errors, touched } }: FieldProps) => ( +

    + {label && ( + + )} +

    The default startup command that should be used for new servers created with this Egg. You can change this per-server as needed.

    +
    + +
    + +

    Additional features belonging to the egg. Useful for configuring additional panel modifications.

    +
    +
    @@ -161,5 +169,10 @@ $(this).val(prepend + ' ' + append); } }); + $('#pConfigFeatures').select2({ + tags: true, + selectOnClose: false, + tokenSeparators: [',', ' '], + }); @endsection diff --git a/resources/views/admin/eggs/view.blade.php b/resources/views/admin/eggs/view.blade.php index b999d2a910..50b69c3793 100644 --- a/resources/views/admin/eggs/view.blade.php +++ b/resources/views/admin/eggs/view.blade.php @@ -114,6 +114,17 @@

    The default startup command that should be used for new servers using this Egg.

    +
    + +
    + +

    Additional features belonging to the egg. Useful for configuring additional panel modifications.

    +
    +
    @@ -202,5 +213,10 @@ $(this).val(prepend + ' ' + append); } }); + $('#pConfigFeatures').select2({ + tags: true, + selectOnClose: false, + tokenSeparators: [',', ' '], + }); @endsection